[
  {
    "path": ".gitattributes",
    "content": "* text eol=lf\n*.exe binary\n*.dll binary\n*.e32 binary\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: [coreybutler]\npatreon: # coreybutler\nopen_collective: # Replace with a single Open Collective username\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\ncustom: # Replace with a single custom sponsorship URL\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: \"🕷️ Bug Report\"\ndescription: Report errors or unexpected behavior\ntitle: \"[Issue]: \"\nlabels:\n  - \"needs review\"\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        ## Before submitting your issue, please do the following:\n\n        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_).\n        1. **Run `nvm debug` (v1.1.11+) to see if you can resolve your issue yourself.**\n        1. Review the **[common issues and resolutions](https://github.com/coreybutler/nvm-windows/wiki/Common-Issues)** wiki page.\n        1. **Search existing issues to see if your issue has already been opened/closed (help prevent duplicates).**\n        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.\n        1. **[Read the README](https://github.com/coreybutler/nvm-windows)**. We took the time to write it for you. Many people ignore this.\n        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.\n        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.\n  - type: textarea\n    id: issue\n    attributes:\n      label: What happened?\n      description: Please provide a clear and concise description of what happened.\n      placeholder: Tell us what you see!\n    validations:\n      required: true\n  - type: textarea\n    id: expected\n    attributes:\n      label: What did you expect to happen?\n      description: Please provide a clear and concise description of what you expected to happen.\n      placeholder: Tell us what you thought you'd see!\n    validations:\n      required: true\n  - type: dropdown\n    id: version\n    attributes:\n      label: Version\n      description: What version are you running? Run `nvm version` to find out. **Please don't assume. Check the version.**\n      options:\n        - 1.2.0 or newer (Default)\n        - 1.1.12\n        - 1.1.11\n        - 1.1.10\n        - 1.1.9\n        - 1.1.8\n        - 1.1.7\n        - 1.1.6 or older\n    validations:\n      required: true\n  - type: dropdown\n    id: os-version\n    attributes:\n      label: Which version of Windows?\n      multiple: true\n      options:\n        - Windows 11+ (Default)\n        - Windows 10\n        - Windows 8.1\n        - Windows 8\n        - Windows 7\n        - Windows Server 2022\n        - Windows Server 2019\n        - Older\n  - type: dropdown\n    id: os-language\n    attributes:\n      label: Which locale?\n      description: Are you running a non-English version of Windows?\n      multiple: false\n      options:\n        - English (Default)\n        - Western European\n        - Asian\n        - Other Non-English\n  - type: dropdown\n    attributes:\n      label: Which shell are you running NVM4W in?\n      options:\n        - \"Command Prompt\"\n        - PowerShell\n        - WSL or WSL2\n        - Cygwin\n        - Mingw64\n        - ConEmu\n        - Commander\n        - Hyper\n        - Other\n      multiple: true\n  - type: dropdown\n    id: permissions\n    attributes:\n      label: User Permissions?\n      options:\n        - Administrative Privileges, Elevated\n        - Administrative Privileges, Non-Elevated\n        - Standard User, Elevated\n        - Standard Use, Non-Elevated\n        - I don't know (Default)\n        - Other, please describe\n    validations:\n      required: true\n  - type: dropdown\n    id: developer-mode\n    attributes:\n      label: Is Developer Mode enabled?\n      description: See [Enable your device for development](https://docs.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development).\n      options:\n        - \"Yes\"\n        - No (Default)\n  - type: textarea\n    id: logs\n    attributes:\n      label: Relevant log/console output\n      description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.\n      render: Shell\n    validations:\n      required: false\n  - type: textarea\n    attributes:\n      label: Debug Output\n      description: |\n        If possible, please provide the output of `nvm debug`.\n\n        The output of `nvm debug` looks like this\n        ```\n        <username> is not using admin or elevated rights.\n\n        PowerShell\n        Windows Developer Mode: UNKNOWN (user cannot read registry)\n\n        NVM4W Version:      1.2.0\n        NVM4W Path:         C:\\Users\\username\\AppData\\Roaming\\nvm\\nvm.exe\n        NVM4W Settings:     C:\\Users\\username\\AppData\\Roaming\\nvm\\settings.txt\n        NVM_HOME:           C:\\Users\\uername\\AppData\\Roaming\\nvm\n        NVM_SYMLINK:        C:\\Program Files\\nodejs\n        Node Installations: C:\\Users\\username\\AppData\\Roaming\\nvm\n\n        Active Node.js Version: v20.7.0\n\n        No problems detected.\n\n        Find help at https://github.com/coreybutler/nvm-windows/wiki/Common-Issues\n        ```\n\n        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+.\n\n        ```sh\n        Running version 1.1.12.\n\n        Usage:\n\n          nvm arch                     : Show if node is running in 32 or 64 bit mode.\n          nvm current                  : Display active version.\n          nvm debug                    : Check the NVM4W process for known problems (troubleshooter).\n          nvm install <version> [arch] : The version can be a specific version, \"latest\" for the latest current version, or \"lts\" for the\n                                         most recent LTS version. Optionally specify whether to install the 32 or 64 bit version (defaults\n                                         to system arch). Set [arch] to \"all\" to install 32 AND 64 bit versions.\n                                         Add --insecure to the end of this command to bypass SSL validation of the remote download server.\n          nvm list [available]         : List the node.js installations. Type \"available\" at the end to see what can be installed. Aliased as ls.\n          nvm on                       : Enable node.js version management.\n          nvm off                      : Disable node.js version management.\n        ```\n      render: Shell\n    validations:\n      required: true\n  - type: textarea\n    id: misc\n    attributes:\n      label: Anything else?\n      description: |\n        Links? References? Anything that will give us more context about the issue you are encountering!\n\n        Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.\n    validations:\n      required: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: \"\\u003F General Question\"\n    url: https://github.com/coreybutler/nvm-windows/discussions\n    about: Discuss features or general questions"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feaure_request.yml",
    "content": "name: \"⭐ Feature or enhancement request\"\ntitle: \"[Feature]: \"\ndescription: Propose something new.\nlabels:\n  - \"needs review\"\n  - \"enhancement request\"\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        **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.\n\n        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).\n\n        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)\n  - type: textarea\n    attributes:\n      label: Description of the new feature / enhancement\n      placeholder: What is the expected behavior of the proposed feature?\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Scenario when this would be used?\n      placeholder: What is the scenario this would be used? Why is this important to your workflow as a power user?\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Supporting information\n      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.\"\n    validations:\n      required: false\n  - type: markdown\n    attributes:\n      value: Please limit one request per issue."
  },
  {
    "path": ".github/OLD_ISSUE_TEMPLATE",
    "content": "<!-- \n\n👋 WELCOME\n \n===============================================================================\nPLEASE, pretty please, make sure your issue hasn't already been addressed.\n===============================================================================\n\nHere's how:\n\n- Read the README to be aware of npm gotchas & antivirus issues.\n- Review the wiki at https://github.com/coreybutler/nvm-windows/wiki\n- Verify you're running NVM4W with a user account that has administrative privileges.\n- Search the issues (open and closed) to make sure this isn't a duplicate.\n- Review the Discussion board.\n\n--------------------------------------------------------------------------------\n|    If this isn't a bug report, please use the discussion feature instead!    |\n--------------------------------------------------------------------------------\n\nNOTICE:\n\nWindows 7 is not technically supported. This software may work on\nWindows 7, but no fixes will be made for it. Microsoft ended EXTENDED support\nfor Windows 7 on January 14, 2020. There is a note about Windows 7 in the wiki.\n\nOnly the standard terminal app and Powershell are supported. Non-standard shell\nenvironments (Cmder, Hyper, Cygwin, git) often manipulate file paths and other\nfeatures. While an attempt is made to make NVM4W work in as many environments\nas possible, we cannot realistically test every non-standard shell.\n\n💬 Failure to fill in the basic questions may result in slow/no response or\ndismissal of the issue. This project has had too many empty issues with nothing\nmore than a title and we cannot guess about your environment. Save everyone some\ntime and provide answers that will help the project maintainer(s) isolate and\nfix the issue.\n-->\n\n**NVM4W version:** <!-- Run `nvm version` to find out. -->\n\n### My Environment\n\n<!-- \n💬 Please provide only the environments you're experiencing a problem with.\n\nExample:\n- Windows 8\n- Windows 8.1\n- Windows 10\n- Windows 10 IoT Core\n- Windows Server 2012\n- Windows Server 2012 R2\n- Windows Server 2016\n\n🤔 If your Windows installation is non-English, please mention it.\n-->\n\n### Expected Behavior\n\n<!-- 💬 Uncomment and fill me in... -->\n\n### Actual Behavior\n\n<!-- 💬 Uncomment and fill me in... -->\n\n### Steps to reproduce the problem:\n\n<!-- 💬 Uncomment and fill me in... -->\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"daily\"\n"
  },
  {
    "path": ".github/workflows/autotag.yml",
    "content": "name: Autotag\n\non:\n  push:\n    branches:\n      - main\n      - master\n\njobs:\n  autotag:\n    runs-on: ubuntu-latest\n\n    outputs:\n      tag_name: ${{ steps.create_tag.outputs.tag_name}}\n\n    steps:\n    - name: Checkout code\n      uses: actions/checkout@v4\n\n    - name: Identify version\n      id: read_version\n      run: |\n        cd ./src\n        echo identifying version...\n        VERSION=$(jq -r '.version' manifest.json)\n        echo \"version=$VERSION\" >> $GITHUB_OUTPUT\n        cd ../\n\n    - name: Check if tag exists\n      id: check_tag\n      run: |\n        VERSION=${{ steps.read_version.outputs.version }}\n        if git rev-parse \"$VERSION\" >/dev/null 2>&1; then\n          echo \"Tag $VERSION already exists.\"\n          echo \"tag_exists=true\" >> $GITHUB_OUTPUT\n        else\n          echo \"Tag $VERSION does not exist.\"\n          echo \"tag_exists=false\" >> $GITHUB_OUTPUT\n        fi\n\n    - name: Import GPG Key\n      uses: crazy-max/ghaction-import-gpg@v6.2.0\n      with:\n        gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}\n        passphrase: ${{ secrets.GPG_PASSPHRASE }}\n        git_tag_gpgsign: true\n        git_user_signingkey: true\n\n    - name: Create tag\n      if: success() && steps.check_tag.outputs.tag_exists == 'false'\n      id: create_tag\n      run: |\n        VERSION=${{ steps.read_version.outputs.version }}\n        git tag -s \"$VERSION\" -m \"Release $VERSION\"\n        git push origin \"$VERSION\"\n        echo \"$VERSION tag created.\"\n"
  },
  {
    "path": ".github/workflows/bot.yml",
    "content": "name: 'Close stale issues and PR'\non:\n  schedule:\n    - cron: '30 1 * * *'\n\njobs:\n  stale:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/stale@v9\n        with:\n          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.'\n          stale-pr-message: 'This PR is stale because it has been open 45 days with no activity.'\n          close-issue-message: 'This issue was closed because it has been stalled for 7 days with no activity.'\n          days-before-stale: 30\n          days-before-close: 7\n          days-before-pr-close: -1\n          start-date: '2021-09-15T05:00:00Z'\n          exempt-all-assignees: true\n          exempt-all-milestones: true\n          exempt-issue-labels: 'bug,not stale,help wanted,consider for rt'\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\n\non:\n  workflow_run:\n    workflows: [\"Autotag\"]\n    types:\n      - completed\n\njobs:\n  build:\n    runs-on: windows-latest\n\n    if: ${{ github.event.workflow_run.conclusion == 'success' }}\n\n    permissions:\n      id-token: write\n      contents: write\n      attestations: write\n\n    env:\n      GO_VERSION: 1.23\n      QUIKGO_VERSION: 1.2.6\n      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      AUTHOR_BRIDGE_TOKEN: ${{ secrets.AUTHOR_BRIDGE_TOKEN }}\n\n    steps:\n    - name: Checkout code\n      uses: actions/checkout@v4\n\n    - name: Extract Tag from Event\n      id: extract_tag\n      shell: pwsh\n      run: |\n        Write-Host \"Event payload:\"\n        Get-Content $env:GITHUB_EVENT_PATH\n\n        # Extract the commit SHA from the event payload\n        $COMMIT_SHA = (Get-Content -Raw $env:GITHUB_EVENT_PATH | ConvertFrom-Json).workflow_run.head_commit.id\n\n        if (-not $COMMIT_SHA) {\n          Write-Host \"Error: No commit SHA found in the event payload.\"\n          exit 1\n        }\n\n        Write-Host \"Commit SHA: $COMMIT_SHA\"\n\n        # Fetch tags and find the tag associated with the commit\n        git fetch --tags\n        $TAG_REF = git tag --contains $COMMIT_SHA | ForEach-Object { $_.Trim() } | Select-Object -First 1\n\n        if (-not $TAG_REF) {\n          Write-Host \"Error: No tag found for this commit.\"\n          exit 1\n        }\n\n        Write-Host \"Extracted tag reference: $TAG_REF\"\n        echo \"TAG=$TAG_REF\" | Out-File -Append -FilePath $env:GITHUB_ENV\n\n    - name: Display Tag\n      shell: pwsh\n      run: |\n        Write-Host \"Identified Tag: $env:TAG\"\n\n    - name: Set up Go\n      uses: actions/setup-go@v5\n      with:\n        go-version: '${{ env.GO_VERSION }}'\n\n    - name: Cache Go Modules\n      uses: actions/cache@v4\n      with:\n        path: |\n          ~/.cache/go-build\n          ~/go/pkg/mod\n        key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}\n        restore-keys: |\n          ${{ runner.os }}-go-\n\n    - name: Cache QuikGo\n      uses: actions/cache@v4\n      with:\n        path: |\n          ~/go/bin\n        key: ${{ runner.os }}-qgo-v${{ env.QUIKGO_VERSION }}\n        restore-keys: |\n          ${{ runner.os }}-qgo-\n\n    - name: Install QuikGo\n      run: |\n        if (-not (go list -m github.com/quikdev/go/cmd/qgo@v${{ env.QUIKGO_VERSION }})) {\n          if (-not (go list -m github.com/quikdev/go/cmd/qgo@v${{ env.QUIKGO_VERSION }})) {\n            go install github.com/quikdev/go/cmd/qgo@v${{ env.QUIKGO_VERSION }}\n          } else {\n            echo \"QuikGo version ${{ env.QUIKGO_VERSION }} already installed.\"\n          }\n        } else {\n          echo \"QuikGo version ${{ env.QUIKGO_VERSION }} already installed.\"\n        }\n\n    - name: Confirm QuikGo Installation\n      run: qgo --version\n\n    - name: Build NVM for Windows\n      run: |\n        cd src\n        qgo build --profile=release\n        echo ${{ github.workspace }}\\bin\n        dir ${{ github.workspace }}\\bin\n        cd ..\\\n\n    # - name: Compress Executables\n    #   uses: crazy-max/ghaction-upx@v3\n    #   with:\n    #     version: latest\n    #     files: |\n    #       ${{ github.workspace }}/bin/*.exe\n    #     args: --brute\n\n    - name: Download Resource Hacker\n      run: |\n        Invoke-WebRequest -Uri \"https://www.angusj.com/resourcehacker/resource_hacker.zip\" -OutFile \"resource_hacker.zip\"\n        Expand-Archive -Path \"resource_hacker.zip\" -DestinationPath \"$env:USERPROFILE\\resource_hacker\"\n\n    - name: Apply icon to executable\n      run: |\n        $resourceHackerPath = \"$env:USERPROFILE\\resource_hacker\\ResourceHacker.exe\"\n        $exe = \"${{ github.workspace }}\\bin\\nvm.exe\"\n        $iconFile = \"${{ github.workspace }}\\assets\\nvm.ico\"\n        & $resourceHackerPath -open $exe -save $exe -action add -res $iconFile -mask ICONGROUP,MAINICON\n\n    - name: Sign Executables\n      uses: azure/trusted-signing-action@v0.5.0\n      with:\n        azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}\n        azure-client-id: ${{ secrets.AZURE_CLIENT_ID }}\n        azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}\n        endpoint: ${{ secrets.AZURE_ENDPOINT }}\n        trusted-signing-account-name: ${{ secrets.AZURE_CODE_SIGNING_NAME }}\n        certificate-profile-name: ${{ secrets.AZURE_CERT_PROFILE_NAME }}\n        files-folder: ${{ github.workspace }}\\bin\n        files-folder-filter: exe,dll\n        file-digest: SHA256\n        timestamp-rfc3161: http://timestamp.acs.microsoft.com\n        timestamp-digest: SHA256\n\n    - name: Identify & Download Latest Author-NVM Bridge App (Using GitHub API)\n      id: get_release\n      run: |\n        $repoOwner = \"author\"\n        $repoName = \"author-nvm\"\n        $token = $env:AUTHOR_BRIDGE_TOKEN # Ensure the token is provided as a secret\n\n        # Fetch the latest release information\n        Write-Host \"GET https://api.github.com/repos/$repoOwner/$repoName/releases\"\n        $response = Invoke-RestMethod -Uri \"https://api.github.com/repos/$repoOwner/$repoName/releases\" `\n                                      -Headers @{ Authorization = \"token $token\"; Accept = \"application/vnd.github.v3+json\"; }\n\n        if ($response -and $response[0].tag_name) {\n            $releaseTag = $response[0].tag_name\n            Write-Host \"Latest release tag identified: $releaseTag\"\n\n            # Fetch the release details for the identified tag\n            Write-Host \"GET https://api.github.com/repos/$repoOwner/$repoName/releases/tags/$releaseTag\"\n            $releaseDetails = Invoke-RestMethod -Uri \"https://api.github.com/repos/$repoOwner/$repoName/releases/tags/$releaseTag\" `\n                                                -Headers @{ Authorization = \"token $token\"; Accept = \"application/vnd.github.v3+json\"; }\n\n            # Extract asset ID for author-nvm.exe\n            $asset = $releaseDetails.assets | Where-Object { $_.name -eq 'author-nvm.exe' } | Select-Object -First 1\n\n            if ($asset -and $asset.id) {\n                $assetId = $asset.id\n                Write-Host \"Asset ID for author-nvm.exe: $assetId\"\n                \"ASSET_ID=$assetId\" | Out-File -Append -FilePath $env:GITHUB_ENV\n\n                # Download asset\n                $assetDownloadUrl = \"https://api.github.com/repos/$repoOwner/$repoName/releases/assets/$assetId\"\n                Write-Host \"GET $assetDownloadUrl\"\n                Invoke-WebRequest -Uri $assetDownloadUrl `\n                                  -Headers @{ Authorization = \"token $token\"; Accept = \"application/octet-stream\"; } `\n                                  -OutFile \"$env:GITHUB_WORKSPACE\\bin\\author-nvm.exe\"\n            } else {\n                Write-Error \"author-nvm.exe not found in the latest release.\"\n                exit 1\n            }\n        } else {\n            Write-Error \"No release tags found in the repository.\"\n            exit 1\n        }\n\n    - name: Generate Core Assets\n      run: |\n        $bin = \"${{ github.workspace }}\\bin\"\n        $license = \"${{ github.workspace }}\\LICENSE\"\n        $source = \"${{ github.workspace }}\\assets\"\n        $target = \"${{ github.workspace }}\\.tmp\"\n        $distros = \"${{ github.workspace }}\\.dist\"\n\n        if (!(Test-Path -Path $target)) {\n          New-Item -ItemType Directory -Path $target\n        }\n\n        # Copy binaries to distribution\n        Get-ChildItem -Path $bin -File | ForEach-Object {\n          Copy-Item -Path $_.FullName -Destination $target\n        }\n\n        # Copy assets to distribution\n        Get-ChildItem -Path $source -File | ForEach-Object {\n          Copy-Item -Path $_.FullName -Destination $target\n        }\n\n        # Copy license to distribution\n        Copy-Item -Path $license -Destination $target\n      shell: pwsh\n\n    - name: Generate Asset Distribution\n      run: |\n        $source = \"${{ github.workspace }}\\.tmp\"\n        $manual = \"${{ github.workspace }}\\.dist\\nvm-noinstall.zip\"\n        $checksum = \"${{ github.workspace }}\\.dist\\nvm-noinstall.zip.checksum.txt\"\n\n        if (Test-Path -Path $manual) {\n          Remove-Item -Path $manual -Force\n        }\n\n        & \"C:\\Program Files\\7-Zip\\7z.exe\" a -tzip $manual \"$source\\*\" -mx=9\n\n        $hash = Get-FileHash -Path $manual -Algorithm MD5\n        $hash.Hash | Out-File -FilePath $checksum -Encoding utf8\n      shell: pwsh\n\n    # - name: Install Inno Setup\n    #   run: |\n    #     choco install innosetup -y\n\n    #     # Verify installation\n    #     if (!(Test-Path \"C:\\Program Files (x86)\\Inno Setup 6\\ISCC.exe\")) {\n    #       Write-Error \"Inno Setup installation failed.\"\n    #       exit 1\n    #     }\n\n    - name: Generate Installer\n      run: |\n        $iss = \"${{ github.workspace }}\\nvm.iss\"\n        $distros = \"${{ github.workspace }}\\.dist\"\n\n        if (!(Test-Path -Path $iss)) {\n          Write-Error \"Inno Setup Script file not found: $iss\"\n          exit 1\n        }\n\n        # Replace version placeholder in the .iss file\n        $version = $env:TAG -replace \"^v\", \"\"\n        (Get-Content -Path $iss) -replace \"{{VERSION}}\", $version | Set-Content -Path $iss\n\n        # Output the file to assure replacements worked\n        Get-Content $iss\n\n        # Get the files and directories in the source directory\n        $items = Get-ChildItem -Path $source -Recurse\n        $source = \"${{ github.workspace }}\\.tmp\"\n        $destination = \"${{ github.workspace }}\\bin\"\n\n        foreach ($item in $items) {\n            # Construct the target path\n            $target = $item.FullName -replace [regex]::Escape($source), [regex]::Escape($destination)\n\n            if ($item.PSIsContainer) {\n                # Create directories if they don't exist\n                if (-not (Test-Path -Path $target)) {\n                    New-Item -ItemType Directory -Path $target\n                }\n            } else {\n                # Copy files if they don't already exist\n                if (-not (Test-Path -Path $target)) {\n                    Copy-Item -Path $item.FullName -Destination $target\n                }\n            }\n        }\n\n        Write-Host \"Available assets in bin directory:\"\n        dir ${{ github.workspace }}\\bin\n\n        Write-Host \"Available assets in .tmp directory:\"\n        dir ${{ github.workspace }}\\.tmp\n\n        Write-Host \"Available build tools:\"\n        dir ${{ github.workspace }}\\assets\\buildtools\n\n        ${{ github.workspace }}\\assets\\buildtools\\iscc.exe \"$iss\" \"/o$distros\"\n        # iscc \"$iss\" \"/o$distros\"\n      shell: pwsh\n\n    - name: Sign Installer\n      uses: azure/trusted-signing-action@v0.5.0\n      with:\n        azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}\n        azure-client-id: ${{ secrets.INSTALLER_AZURE_CLIENT_ID }}\n        azure-client-secret: ${{ secrets.INSTALLER_AZURE_CLIENT_SECRET }}\n        endpoint: ${{ secrets.AZURE_ENDPOINT }}\n        trusted-signing-account-name: ${{ secrets.AZURE_CODE_SIGNING_NAME }}\n        certificate-profile-name: ${{ secrets.AZURE_CERT_PROFILE_NAME }}\n        files-folder: ${{ github.workspace }}\\.dist\n        files-folder-filter: exe,dll\n        file-digest: SHA256\n        timestamp-rfc3161: http://timestamp.acs.microsoft.com\n        timestamp-digest: SHA256\n\n    - name: Generate Official Distribution\n      run: |\n        $source = \"${{ github.workspace }}\\.dist\\nvm-setup.exe\"\n        $distro = \"${{ github.workspace }}\\.dist\\nvm-setup.zip\"\n        $checksum = \"${{ github.workspace }}\\.dist\\nvm-setup.zip.checksum.txt\"\n\n        if (!(Test-Path -Path $source)) {\n          Write-Error \"Source file for ZIP not found: $source\"\n          exit 1\n        }\n\n        & \"C:\\Program Files\\7-Zip\\7z.exe\" a -tzip $distro \"$source\" -mx=9\n\n        if (!(Test-Path -Path $distro)) {\n          Write-Error \"ZIP file generation failed.\"\n          exit 1\n        }\n\n        $hash = Get-FileHash -Path $distro -Algorithm MD5\n        $hash.Hash | Out-File -FilePath $checksum -Encoding utf8\n      shell: pwsh\n\n    - name: Attestation\n      uses: actions/attest-build-provenance@v2\n      id: attest\n      with:\n        subject-path: |\n          ${{ github.workspace }}/bin/*.exe\n          ${{ github.workspace }}/.dist/*.exe\n          ${{ github.workspace }}/.dist/*.zip\n          ${{ github.workspace }}/.dist/*.checksum.txt\n        github-token: ${{ secrets.GITHUB_TOKEN }}\n\n    - name: Release\n      uses: softprops/action-gh-release@v2\n      if: success()\n      with:\n        tag_name: ${{ env.TAG }}\n        files: |\n          ${{ github.workspace }}/.dist/*\n        fail_on_unmatched_files: true\n        generate_release_notes: true\n        prerelease: false\n        draft: false\n        make_latest: true\n\n    - name: Add Attestation Report to Release Notes\n      if: success()\n      uses: softprops/action-gh-release@v2\n      with:\n        tag_name: ${{ env.TAG }}\n        body: |\n          <br/>:office: **[Attestation Report](${{ steps.attest.outputs.attestation-url }})**\n        append_body: true\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n  rollback:\n    runs-on: ubuntu-latest\n    needs: build\n    if: failure()\n\n    steps:\n    - name: Checkout code\n      uses: actions/checkout@v4\n      with:\n        fetch-depth: 0  # Ensures that all references, including tags, are fetched\n        ref: ${{ github.event.workflow_run.head_sha }}\n\n    - name: Show current commit\n      run: |\n        git log -1 --decorate\n        git tag --contains HEAD\n\n    - name: Get the commit associated with the trigger\n      id: get_commit\n      run: |\n        if [ -f \"$GITHUB_EVENT_PATH\" ]; then\n          # Try to get the head SHA from workflow_run\n          COMMIT_SHA=$(jq -r '.head_commit.id' < \"$GITHUB_EVENT_PATH\")\n          if [ \"$COMMIT_SHA\" = \"null\" ]; then\n            COMMIT_SHA=$(jq -r '.workflow_run.head_commit.id' < \"$GITHUB_EVENT_PATH\")\n          fi\n          if [ -z \"$COMMIT_SHA\" ] || [ \"$COMMIT_SHA\" = \"null\" ]; then\n            echo \"Error: Unable to retrieve commit SHA from GITHUB_EVENT_PATH\"\n            cat \"$GITHUB_EVENT_PATH\"\n            echo \"HEAD: $(git rev-parse HEAD)\"\n            exit 1\n          fi\n          echo \"Commit SHA: $COMMIT_SHA\"\n          echo \"COMMIT_SHA=$COMMIT_SHA\" >> $GITHUB_ENV\n        else\n          echo \"Error: GITHUB_EVENT_PATH not found\"\n          exit 1\n        fi\n\n    - name: Fetch the tag associated with the commit using git\n      id: fetch_tag\n      run: |\n        # Debugging: print all tags in the repository\n        git tag\n\n        # Get the tag associated with the commit\n        echo \"Finding tag associated with the commit...\"\n        TAG=$(git tag --contains \"$COMMIT_SHA\" | head -n 1)\n        echo \"Tag associated with the commit is: $TAG\"\n\n        # Debugging: Check if the tag is found\n        if [ -z \"$TAG\" ]; then\n          echo \"No tag found for this commit.\"\n        else\n          echo \"Found tag: $TAG\"\n        fi\n\n        echo \"TAG=$TAG\" >> $GITHUB_ENV\n\n    - name: Delete release\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      run: |\n        # Use the TAG from the environment variable\n        if [ -z \"$TAG\" ]; then\n          echo \"No tag to delete.\"\n          exit 1\n        fi\n\n        RELEASE_ID=$(gh release view \"$TAG\" --json id --jq '.id' || echo \"\")\n        if [ -n \"$RELEASE_ID\" ]; then\n          gh release delete \"$TAG\" --yes\n          echo \"Release $TAG deleted successfully.\"\n        else\n          echo \"Release $TAG not found.\"\n        fi\n\n    - name: Delete tag associated with release\n      run: |\n        # Use the TAG from the environment variable\n        if [ -z \"$TAG\" ]; then\n          echo \"No tag to delete.\"\n          exit 1\n        fi\n\n        git tag -d \"$TAG\"\n        git push --delete origin \"$TAG\"\n\n    - name: Notify Rollback\n      run: echo \"Rollback completed for release $TAG\""
  },
  {
    "path": ".github/workflows/scanner.yml",
    "content": "name: Virus Scan\n\non:\n  workflow_run:\n    workflows: [\"Release\"]\n    types:\n      - completed\n\njobs:\n  scan:\n    if: ${{ github.event.workflow_run.conclusion }} == 'success'\n\n    runs-on: ubuntu-latest\n\n    steps:\n    - name: Get latest release details\n      id: get_latest_release\n      uses: actions/github-script@v7\n      with:\n        script: |\n          const { data: latestRelease } = await github.rest.repos.getLatestRelease({\n            owner: context.repo.owner,\n            repo: context.repo.repo\n          });\n          core.setOutput('tag', latestRelease.tag_name);\n          core.setOutput('assets', JSON.stringify(latestRelease.assets));\n\n    - name: Download release assets\n      env:\n        ASSETS_JSON: ${{ steps.get_latest_release.outputs.assets }}\n      run: |\n        echo \"Assets: $ASSETS_JSON\"\n        for asset in $(echo $ASSETS_JSON | jq -r '.[].browser_download_url'); do\n          echo \"Downloading $asset\"\n          curl -L -O \"$asset\"\n        done\n\n    - name: Extract Main Executables\n      run: |\n        unzip nvm-noinstall.zip -d ./\n\n    - name: List downloaded assets\n      run: ls -alh ./\n\n    - name: Scan with VirusTotal\n      uses: WoozyMasta/virustotal-action@v1.0.0\n      id: scan\n      with:\n        vt_api_key: ${{ secrets.VIRUSTOTAL_API_KEY }}\n        github_token: ${{ secrets.GITHUB_TOKEN }}\n        file_globs: |\n          nvm.exe\n          author-nvm.exe\n          nvm-setup.exe\n\n    - name: Generate release notes\n      id: release_notes\n      run: |\n        input_list=\"${{ steps.scan.outputs.results }}\"\n        notes=\":bulb: **Antivirus Report**<br/><ul>\"\n\n        # Process each item in the list\n        IFS=',' read -ra ITEMS <<< \"$input_list\"\n        for item in \"${ITEMS[@]}\"; do\n          filename=$(echo \"$item\" | cut -d'/' -f1)\n          id=$(echo \"$item\" | cut -d'/' -f2)\n          notes=\"${notes}<li><a href='https://www.virustotal.com/gui/file-analysis/${id}'>${filename}</a></li>\"\n        done\n\n        notes=\"${notes}</ul>\"\n\n        # Output the result\n        echo \"Generated Notes:\"\n        echo \"$notes\"\n        echo \"NOTES=$notes\" >> $GITHUB_OUTPUT\n\n    - name: Update release body\n      uses: softprops/action-gh-release@v2\n      with:\n        tag_name: ${{ steps.get_latest_release.outputs.tag }}\n        body: |\n          ${{ steps.release_notes.outputs.NOTES }}\n        append_body: true\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n    # - name: Scan with VirusTotal\n    #   uses: crazy-max/ghaction-virustotal@v4\n    #   with:\n    #     vt_api_key: ${{ secrets.VIRUSTOTAL_API_KEY }}\n    #     files: |\n    #       *.exe\n    #       .exe$\n    #     update_release_body: true\n    #     github_token: ${{ secrets.GITHUB_TOKEN }}\n    #     request_rate: 4"
  },
  {
    "path": ".github/workflows/winget.yml",
    "content": "name: Publish to Winget\n\non:\n  release:\n    types: [released]\n\njobs:\n  publish:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: vedantmgoyal2009/winget-releaser@v2\n        with:\n          identifier: CoreyButler.NVMforWindows\n          version: ${{ github.event.release.tag_name }}\n          installers-regex: 'nvm-setup\\.exe$'\n          token: ${{ secrets.WINGET_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# 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# Architecture specific extensions/prefixes\n*.[568vq]\n[568vq].out\n\n*.cgo1.go\n*.cgo2.c\n_cgo_defun.c\n_cgo_gotypes.go\n_cgo_export.*\n\n_testmain.go\n\n*.exe\n!utilities/**/*.exe\n*.test\n*.prof\n\n**/.*\n\ndist\nsrc/v*\nbin/*.exe\nbin/*.log\nbin/LICENSE\n!bin/buildtools/*\n!assets/buildtools/*\nbin/*.zip\nbin/nvm/*\n\n!.gitignore\n!.gitattributes\n!.github\ntests\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "## Before you submit an issue....\n\nThere have been a lot of duplicate issues filed. Please do a simple search for your issue before posting something new.\n\nIf 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.\n\n## Common Issues & Resolutions\n\nSee the [Common Issues](https://github.com/coreybutler/nvm-windows/wiki/Common-Issues) wiki page before submitting.\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2025 Author Software Inc.\nCopyright (c) 2022 Ecor Ventures LLC.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<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>\n<br/><br/>\n<h1 align=\"center\">NVM for Windows</h1>\n\n<div align=\"center\">\n  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/>\n\n<details>\n<summary><b>This is not the same thing as nvm!</b> (expand for details)</summary>\n\n_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).\n</details>\n\n[![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)\n<br/>\n[![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/))\n<details>\n<summary><b>Latest vs Stable?</b></summary>\n  \nThe 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.\n\nSome 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.\n</details>\n</div>\n\n<div align=\"center\">\n<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>\n</div>\n\n<h5 align=\"center\">Sponsors</h5>\n\n<div align=\"center\">\n  <table cellpadding=\"5\" cellspacing=\"0\" border=\"0\" align=\"center\">\n    <tr>\n      <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>\n      <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>\n      <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>\n    </tr>\n    <tr>\n      <td colspan=\"4\" align=\"center\">\n        <a href=\"https://github.com/sponsors/coreybutler\"><img src=\"https://img.shields.io/github/sponsors/coreybutler?label=Individual%20Sponsors&logo=github&style=social\"/></a>\n        &nbsp;<a href=\"https://github.com/sponsors/coreybutler\"><img src=\"https://img.shields.io/badge/-Become%20a%20Sponsor-yellow\"/></a>\n      </td>\n    </tr>\n    <tr>\n      <td colspan=\"4\" align=\"center\">\n        <img src=\"https://github.blog/wp-content/uploads/2020/09/github-stars-logo_Color.png\" width=\"50\"/><br/>\n        <b>Can't sponsor?</b><br/>Consider <a href=\"https://stars.github.com/nominate/\" target=\"_blank\">nominating @coreybutler for a Github star</a>.\n      </td>\n    </tr>\n  </table>\n</div>\n<br/>\n\n<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>\n\n<br/>\n<table style=\"background-color:red;padding:6px;border-radius:3px;\">\n  <tr><td>\n    <h3>Seeking Feedback:</h3>\n    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!\n    <h3></h3>\n  </td></tr>\n</table>\n\n## Overview\n\nManage multiple installations of node.js on a Windows computer.\n\n**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)!\n\nThis 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`.\n\n![NVM for Windows](https://github.com/coreybutler/staticassets/raw/master/images/nvm-1.1.8-screenshot.jpg)\n\nThere 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.\n\n![Switch between stable and unstable versions.](https://github.com/coreybutler/staticassets/raw/master/images/nvm-usage-highlighted.jpg)\n\n### Installation & Upgrades\n\n#### :star: :star: Uninstall any pre-existing Node installations!! :star: :star:\n\nThe 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.\n\n_Permission Problems_\nFor 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.\n\n_PATH Installation Problems_\nIf 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`).\n\n_PATH Conflicts_\nIf 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.\n\nFor 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.\n\n:eyes: **Backup any global `npmrc` config** :eyes:\n(e.g. `%AppData%\\npm\\etc\\npmrc`)\n\nAlternatively, copy the settings to the user config `%UserProfile%\\.npmrc`. Delete the existing npm install location (e.g. `%AppData%\\npm`) to prevent global module conflicts.\n\n#### Install nvm-windows\n\nUse 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.\n\n_If NVM4W doesn't appear to work immediately after installation, restart the terminal/powershell (not the whole computer)._\n\n![NVM for Windows Installer](https://github.com/coreybutler/staticassets/raw/master/images/nvm-installer.jpg)\n\n#### Reinstall any global utilities\n\nAfter install, reinstalling global utilities (e.g. yarn) will have to be done for each installed version of node:\n\n```\nnvm use 14.0.0\nnpm install -g yarn\nnvm use 12.0.1\nnpm install -g yarn\n```\n\n### Upgrading nvm-windows\n\n:bulb: _As of v1.1.8, there is an upgrade utility that will automate the upgrade process._\n\n**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.\n\n### Usage\n\n**nvm-windows runs in an Admin shell**. You'll need to start `powershell` or Command Prompt as Administrator to use nvm-windows\n\nNVM for Windows is a command line tool. Simply type `nvm` in the console for help. The basic commands are:\n\n- **`nvm arch [32|64]`**: Show if node is running in 32 or 64 bit mode. Specify 32 or 64 to override the default architecture.\n- **`nvm debug`**: Check the NVM4W process for known problems.\n- **`nvm current`**: Display active version.\n- **`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.\n- **`nvm list [available]`**: List the node.js installations. Type `available` at the end to show a list of versions available for download.\n- **`nvm on`**: Enable node.js version management.\n- **`nvm off`**: Disable node.js version management (does not uninstall anything).\n- **`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.\n- **`nvm uninstall <version>`**: Uninstall a specific version.\n- **`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).\n- **`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.\n- **`nvm version`**: Displays the current running version of NVM for Windows.\n- **`nvm node_mirror <node_mirror_url>`**: Set the node mirror.People in China can use *https://npmmirror.com/mirrors/node/*\n- **`nvm npm_mirror <npm_mirror_url>`**: Set the npm mirror.People in China can use *https://npmmirror.com/mirrors/npm/*\n\n### :warning: Gotcha!\n\nPlease 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.\n\n### :name_badge: Antivirus\n\nUsers 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.\n\n**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.\n\n> 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.\n\n### Using Yarn\n\n**tldr;** `npm i -g yarn`\n\nSee the [wiki](https://github.com/coreybutler/nvm-windows/wiki/Common-Issues#how-do-i-use-yarn-with-nvm-windows) for details.\n\n### Build from source\n\n- Install go from http://golang.org\n- Download source / Git Clone the repo\n- Change GOARCH to amd64 in build.bat if you feel like building a 64-bit executable\n- Fire up a Windows command prompt and change directory to project dir\n- Execute `go get github.com/blang/semver`\n- Execute `go get github.com/olekukonko/tablewriter`\n- Execute `build.bat`\n- Check the `dist`directory for generated setup program.\n\n---\n\n## :bulb: Why another version manager?\n\nThere are several version managers for node.js. Tools like [nvm](https://github.com/creationix/nvm) and [n](https://github.com/tj/n)\nonly 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)\nare both designed for Windows. So, why another version manager for Windows?\n\nThe 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.\n\nAdditionally, 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.\n\n## :bulb: What's the big difference?\n\nFirst 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.\n\nThe 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.\n\nThe 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.\n\nIn 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.\n\nNVM for Windows comes with an installer, courtesy of a byproduct of my work on [Fenix Web Server](https://preview.fenixwebserver.com).\n\nOverall, this project brings together some ideas, a few battle-hardened pieces of other modules, and support for newer versions of node.\n\nNVM 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.\n\n## Motivation\n\nI 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.\n\n## Why Go? Why not Node?\n\nI 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.\n\n## :pray: Thanks\n\nThanks to everyone who has submitted issues on and off Github, made suggestions, and generally helped make this a better project. Special thanks to\n\n- [@vkbansal](https://github.com/vkbansal), who provided significant early feedback throughout the early releases.\n- [@rainabba](https://github.com/rainabba) and [@sullivanpt](https://github.com/sullivanpt) for getting Node v4 support integrated.\n- [@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)).\n- [ajyong](https://github.com/ajyong) who sponsored the code signing certificate in late 2021.\n\n<br/>\n\n![Contributors](https://contrib.rocks/image?repo=coreybutler/nvm-windows)\n"
  },
  {
    "path": "SUPPORT.md",
    "content": "# Need Help?\n\nIf you're running into problems using NVM4W, try the following resources.\n\n1. Review the [wiki](https://github.com/coreybutler/nvm-windows/wiki).\n1. Join ongoing [discussions](https://github.com/coreybutler/nvm-windows/discussions).\n1. Search [closed issues](https://github.com/coreybutler/nvm-windows/issues?q=is%3Aissue+is%3Aclosed) for existing solutions.\n1. Search [StackOverflow ([nvm-windows])](https://stackoverflow.com/questions/tagged/nvm-windows) for answers.\n\nThere 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.\n\nIf none of these things help and you suspect a bug, file an issue.\n\nYou can also try reaching out to [@goldglovecb](https://twitter.com/goldglovecb) on Twitter.\n"
  },
  {
    "path": "assets/buildtools/Default.isl",
    "content": "﻿; *** Inno Setup version 6.1.0+ English messages ***\n;\n; To download user-contributed translations of this file, go to:\n;   https://jrsoftware.org/files/istrans/\n;\n; Note: When translating this text, do not add periods (.) to the end of\n; messages that didn't have them already, because on those messages Inno\n; Setup adds the periods automatically (appending a period would result in\n; two periods being displayed).\n\n[LangOptions]\n; The following three entries are very important. Be sure to read and \n; understand the '[LangOptions] section' topic in the help file.\nLanguageName=English\nLanguageID=$0409\nLanguageCodePage=0\n; If the language you are translating to requires special font faces or\n; sizes, uncomment any of the following entries and change them accordingly.\n;DialogFontName=\n;DialogFontSize=8\n;WelcomeFontName=Verdana\n;WelcomeFontSize=12\n;TitleFontName=Arial\n;TitleFontSize=29\n;CopyrightFontName=Arial\n;CopyrightFontSize=8\n\n[Messages]\n\n; *** Application titles\nSetupAppTitle=Setup\nSetupWindowTitle=Setup - %1\nUninstallAppTitle=Uninstall\nUninstallAppFullTitle=%1 Uninstall\n\n; *** Misc. common\nInformationTitle=Information\nConfirmTitle=Confirm\nErrorTitle=Error\n\n; *** SetupLdr messages\nSetupLdrStartupMessage=This will install %1. Do you wish to continue?\nLdrCannotCreateTemp=Unable to create a temporary file. Setup aborted\nLdrCannotExecTemp=Unable to execute file in the temporary directory. Setup aborted\nHelpTextNote=\n\n; *** Startup error messages\nLastErrorMessage=%1.%n%nError %2: %3\nSetupFileMissing=The file %1 is missing from the installation directory. Please correct the problem or obtain a new copy of the program.\nSetupFileCorrupt=The setup files are corrupted. Please obtain a new copy of the program.\nSetupFileCorruptOrWrongVer=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.\nInvalidParameter=An invalid parameter was passed on the command line:%n%n%1\nSetupAlreadyRunning=Setup is already running.\nWindowsVersionNotSupported=This program does not support the version of Windows your computer is running.\nWindowsServicePackRequired=This program requires %1 Service Pack %2 or later.\nNotOnThisPlatform=This program will not run on %1.\nOnlyOnThisPlatform=This program must be run on %1.\nOnlyOnTheseArchitectures=This program can only be installed on versions of Windows designed for the following processor architectures:%n%n%1\nWinVersionTooLowError=This program requires %1 version %2 or later.\nWinVersionTooHighError=This program cannot be installed on %1 version %2 or later.\nAdminPrivilegesRequired=You must be logged in as an administrator when installing this program.\nPowerUserPrivilegesRequired=You must be logged in as an administrator or as a member of the Power Users group when installing this program.\nSetupAppRunningError=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.\nUninstallAppRunningError=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.\n\n; *** Startup questions\nPrivilegesRequiredOverrideTitle=Select Setup Install Mode\nPrivilegesRequiredOverrideInstruction=Select install mode\nPrivilegesRequiredOverrideText1=%1 can be installed for all users (requires administrative privileges), or for you only.\nPrivilegesRequiredOverrideText2=%1 can be installed for you only, or for all users (requires administrative privileges).\nPrivilegesRequiredOverrideAllUsers=Install for &all users\nPrivilegesRequiredOverrideAllUsersRecommended=Install for &all users (recommended)\nPrivilegesRequiredOverrideCurrentUser=Install for &me only\nPrivilegesRequiredOverrideCurrentUserRecommended=Install for &me only (recommended)\n\n; *** Misc. errors\nErrorCreatingDir=Setup was unable to create the directory \"%1\"\nErrorTooManyFilesInDir=Unable to create a file in the directory \"%1\" because it contains too many files\n\n; *** Setup common messages\nExitSetupTitle=Exit Setup\nExitSetupMessage=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?\nAboutSetupMenuItem=&About Setup...\nAboutSetupTitle=About Setup\nAboutSetupMessage=%1 version %2%n%3%n%n%1 home page:%n%4\nAboutSetupNote=\nTranslatorNote=\n\n; *** Buttons\nButtonBack=< &Back\nButtonNext=&Next >\nButtonInstall=&Install\nButtonOK=OK\nButtonCancel=Cancel\nButtonYes=&Yes\nButtonYesToAll=Yes to &All\nButtonNo=&No\nButtonNoToAll=N&o to All\nButtonFinish=&Finish\nButtonBrowse=&Browse...\nButtonWizardBrowse=B&rowse...\nButtonNewFolder=&Make New Folder\n\n; *** \"Select Language\" dialog messages\nSelectLanguageTitle=Select Setup Language\nSelectLanguageLabel=Select the language to use during the installation.\n\n; *** Common wizard text\nClickNext=Click Next to continue, or Cancel to exit Setup.\nBeveledLabel=\nBrowseDialogTitle=Browse For Folder\nBrowseDialogLabel=Select a folder in the list below, then click OK.\nNewFolderName=New Folder\n\n; *** \"Welcome\" wizard page\nWelcomeLabel1=Welcome to the [name] Setup Wizard\nWelcomeLabel2=This will install [name/ver] on your computer.%n%nIt is recommended that you close all other applications before continuing.\n\n; *** \"Password\" wizard page\nWizardPassword=Password\nPasswordLabel1=This installation is password protected.\nPasswordLabel3=Please provide the password, then click Next to continue. Passwords are case-sensitive.\nPasswordEditLabel=&Password:\nIncorrectPassword=The password you entered is not correct. Please try again.\n\n; *** \"License Agreement\" wizard page\nWizardLicense=License Agreement\nLicenseLabel=Please read the following important information before continuing.\nLicenseLabel3=Please read the following License Agreement. You must accept the terms of this agreement before continuing with the installation.\nLicenseAccepted=I &accept the agreement\nLicenseNotAccepted=I &do not accept the agreement\n\n; *** \"Information\" wizard pages\nWizardInfoBefore=Information\nInfoBeforeLabel=Please read the following important information before continuing.\nInfoBeforeClickLabel=When you are ready to continue with Setup, click Next.\nWizardInfoAfter=Information\nInfoAfterLabel=Please read the following important information before continuing.\nInfoAfterClickLabel=When you are ready to continue with Setup, click Next.\n\n; *** \"User Information\" wizard page\nWizardUserInfo=User Information\nUserInfoDesc=Please enter your information.\nUserInfoName=&User Name:\nUserInfoOrg=&Organization:\nUserInfoSerial=&Serial Number:\nUserInfoNameRequired=You must enter a name.\n\n; *** \"Select Destination Location\" wizard page\nWizardSelectDir=Select Destination Location\nSelectDirDesc=Where should [name] be installed?\nSelectDirLabel3=Setup will install [name] into the following folder.\nSelectDirBrowseLabel=To continue, click Next. If you would like to select a different folder, click Browse.\nDiskSpaceGBLabel=At least [gb] GB of free disk space is required.\nDiskSpaceMBLabel=At least [mb] MB of free disk space is required.\nCannotInstallToNetworkDrive=Setup cannot install to a network drive.\nCannotInstallToUNCPath=Setup cannot install to a UNC path.\nInvalidPath=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\nInvalidDrive=The drive or UNC share you selected does not exist or is not accessible. Please select another.\nDiskSpaceWarningTitle=Not Enough Disk Space\nDiskSpaceWarning=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?\nDirNameTooLong=The folder name or path is too long.\nInvalidDirName=The folder name is not valid.\nBadDirName32=Folder names cannot include any of the following characters:%n%n%1\nDirExistsTitle=Folder Exists\nDirExists=The folder:%n%n%1%n%nalready exists. Would you like to install to that folder anyway?\nDirDoesntExistTitle=Folder Does Not Exist\nDirDoesntExist=The folder:%n%n%1%n%ndoes not exist. Would you like the folder to be created?\n\n; *** \"Select Components\" wizard page\nWizardSelectComponents=Select Components\nSelectComponentsDesc=Which components should be installed?\nSelectComponentsLabel2=Select the components you want to install; clear the components you do not want to install. Click Next when you are ready to continue.\nFullInstallation=Full installation\n; if possible don't translate 'Compact' as 'Minimal' (I mean 'Minimal' in your language)\nCompactInstallation=Compact installation\nCustomInstallation=Custom installation\nNoUninstallWarningTitle=Components Exist\nNoUninstallWarning=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?\nComponentSize1=%1 KB\nComponentSize2=%1 MB\nComponentsDiskSpaceGBLabel=Current selection requires at least [gb] GB of disk space.\nComponentsDiskSpaceMBLabel=Current selection requires at least [mb] MB of disk space.\n\n; *** \"Select Additional Tasks\" wizard page\nWizardSelectTasks=Select Additional Tasks\nSelectTasksDesc=Which additional tasks should be performed?\nSelectTasksLabel2=Select the additional tasks you would like Setup to perform while installing [name], then click Next.\n\n; *** \"Select Start Menu Folder\" wizard page\nWizardSelectProgramGroup=Select Start Menu Folder\nSelectStartMenuFolderDesc=Where should Setup place the program's shortcuts?\nSelectStartMenuFolderLabel3=Setup will create the program's shortcuts in the following Start Menu folder.\nSelectStartMenuFolderBrowseLabel=To continue, click Next. If you would like to select a different folder, click Browse.\nMustEnterGroupName=You must enter a folder name.\nGroupNameTooLong=The folder name or path is too long.\nInvalidGroupName=The folder name is not valid.\nBadGroupName=The folder name cannot include any of the following characters:%n%n%1\nNoProgramGroupCheck2=&Don't create a Start Menu folder\n\n; *** \"Ready to Install\" wizard page\nWizardReady=Ready to Install\nReadyLabel1=Setup is now ready to begin installing [name] on your computer.\nReadyLabel2a=Click Install to continue with the installation, or click Back if you want to review or change any settings.\nReadyLabel2b=Click Install to continue with the installation.\nReadyMemoUserInfo=User information:\nReadyMemoDir=Destination location:\nReadyMemoType=Setup type:\nReadyMemoComponents=Selected components:\nReadyMemoGroup=Start Menu folder:\nReadyMemoTasks=Additional tasks:\n\n; *** TDownloadWizardPage wizard page and DownloadTemporaryFile\nDownloadingLabel=Downloading additional files...\nButtonStopDownload=&Stop download\nStopDownload=Are you sure you want to stop the download?\nErrorDownloadAborted=Download aborted\nErrorDownloadFailed=Download failed: %1 %2\nErrorDownloadSizeFailed=Getting size failed: %1 %2\nErrorFileHash1=File hash failed: %1\nErrorFileHash2=Invalid file hash: expected %1, found %2\nErrorProgress=Invalid progress: %1 of %2\nErrorFileSize=Invalid file size: expected %1, found %2\n\n; *** \"Preparing to Install\" wizard page\nWizardPreparing=Preparing to Install\nPreparingDesc=Setup is preparing to install [name] on your computer.\nPreviousInstallNotCompleted=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].\nCannotContinue=Setup cannot continue. Please click Cancel to exit.\nApplicationsFound=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.\nApplicationsFound2=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.\nCloseApplications=&Automatically close the applications\nDontCloseApplications=&Do not close the applications\nErrorCloseApplications=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.\nPrepareToInstallNeedsRestart=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?\n\n; *** \"Installing\" wizard page\nWizardInstalling=Installing\nInstallingLabel=Please wait while Setup installs [name] on your computer.\n\n; *** \"Setup Completed\" wizard page\nFinishedHeadingLabel=Completing the [name] Setup Wizard\nFinishedLabelNoIcons=Setup has finished installing [name] on your computer.\nFinishedLabel=Setup has finished installing [name] on your computer. The application may be launched by selecting the installed shortcuts.\nClickFinish=Click Finish to exit Setup.\nFinishedRestartLabel=To complete the installation of [name], Setup must restart your computer. Would you like to restart now?\nFinishedRestartMessage=To complete the installation of [name], Setup must restart your computer.%n%nWould you like to restart now?\nShowReadmeCheck=Yes, I would like to view the README file\nYesRadio=&Yes, restart the computer now\nNoRadio=&No, I will restart the computer later\n; used for example as 'Run MyProg.exe'\nRunEntryExec=Run %1\n; used for example as 'View Readme.txt'\nRunEntryShellExec=View %1\n\n; *** \"Setup Needs the Next Disk\" stuff\nChangeDiskTitle=Setup Needs the Next Disk\nSelectDiskLabel2=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.\nPathLabel=&Path:\nFileNotInDir2=The file \"%1\" could not be located in \"%2\". Please insert the correct disk or select another folder.\nSelectDirectoryLabel=Please specify the location of the next disk.\n\n; *** Installation phase messages\nSetupAborted=Setup was not completed.%n%nPlease correct the problem and run Setup again.\nAbortRetryIgnoreSelectAction=Select action\nAbortRetryIgnoreRetry=&Try again\nAbortRetryIgnoreIgnore=&Ignore the error and continue\nAbortRetryIgnoreCancel=Cancel installation\n\n; *** Installation status messages\nStatusClosingApplications=Closing applications...\nStatusCreateDirs=Creating directories...\nStatusExtractFiles=Extracting files...\nStatusCreateIcons=Creating shortcuts...\nStatusCreateIniEntries=Creating INI entries...\nStatusCreateRegistryEntries=Creating registry entries...\nStatusRegisterFiles=Registering files...\nStatusSavingUninstall=Saving uninstall information...\nStatusRunProgram=Finishing installation...\nStatusRestartingApplications=Restarting applications...\nStatusRollback=Rolling back changes...\n\n; *** Misc. errors\nErrorInternal2=Internal error: %1\nErrorFunctionFailedNoCode=%1 failed\nErrorFunctionFailed=%1 failed; code %2\nErrorFunctionFailedWithMessage=%1 failed; code %2.%n%3\nErrorExecutingProgram=Unable to execute file:%n%1\n\n; *** Registry errors\nErrorRegOpenKey=Error opening registry key:%n%1\\%2\nErrorRegCreateKey=Error creating registry key:%n%1\\%2\nErrorRegWriteKey=Error writing to registry key:%n%1\\%2\n\n; *** INI errors\nErrorIniEntry=Error creating INI entry in file \"%1\".\n\n; *** File copying errors\nFileAbortRetryIgnoreSkipNotRecommended=&Skip this file (not recommended)\nFileAbortRetryIgnoreIgnoreNotRecommended=&Ignore the error and continue (not recommended)\nSourceIsCorrupted=The source file is corrupted\nSourceDoesntExist=The source file \"%1\" does not exist\nExistingFileReadOnly2=The existing file could not be replaced because it is marked read-only.\nExistingFileReadOnlyRetry=&Remove the read-only attribute and try again\nExistingFileReadOnlyKeepExisting=&Keep the existing file\nErrorReadingExistingDest=An error occurred while trying to read the existing file:\nFileExistsSelectAction=Select action\nFileExists2=The file already exists.\nFileExistsOverwriteExisting=&Overwrite the existing file\nFileExistsKeepExisting=&Keep the existing file\nFileExistsOverwriteOrKeepAll=&Do this for the next conflicts\nExistingFileNewerSelectAction=Select action\nExistingFileNewer2=The existing file is newer than the one Setup is trying to install.\nExistingFileNewerOverwriteExisting=&Overwrite the existing file\nExistingFileNewerKeepExisting=&Keep the existing file (recommended)\nExistingFileNewerOverwriteOrKeepAll=&Do this for the next conflicts\nErrorChangingAttr=An error occurred while trying to change the attributes of the existing file:\nErrorCreatingTemp=An error occurred while trying to create a file in the destination directory:\nErrorReadingSource=An error occurred while trying to read the source file:\nErrorCopying=An error occurred while trying to copy a file:\nErrorReplacingExistingFile=An error occurred while trying to replace the existing file:\nErrorRestartReplace=RestartReplace failed:\nErrorRenamingTemp=An error occurred while trying to rename a file in the destination directory:\nErrorRegisterServer=Unable to register the DLL/OCX: %1\nErrorRegSvr32Failed=RegSvr32 failed with exit code %1\nErrorRegisterTypeLib=Unable to register the type library: %1\n\n; *** Uninstall display name markings\n; used for example as 'My Program (32-bit)'\nUninstallDisplayNameMark=%1 (%2)\n; used for example as 'My Program (32-bit, All users)'\nUninstallDisplayNameMarks=%1 (%2, %3)\nUninstallDisplayNameMark32Bit=32-bit\nUninstallDisplayNameMark64Bit=64-bit\nUninstallDisplayNameMarkAllUsers=All users\nUninstallDisplayNameMarkCurrentUser=Current user\n\n; *** Post-installation errors\nErrorOpeningReadme=An error occurred while trying to open the README file.\nErrorRestartingComputer=Setup was unable to restart the computer. Please do this manually.\n\n; *** Uninstaller messages\nUninstallNotFound=File \"%1\" does not exist. Cannot uninstall.\nUninstallOpenError=File \"%1\" could not be opened. Cannot uninstall\nUninstallUnsupportedVer=The uninstall log file \"%1\" is in a format not recognized by this version of the uninstaller. Cannot uninstall\nUninstallUnknownEntry=An unknown entry (%1) was encountered in the uninstall log\nConfirmUninstall=Are you sure you want to completely remove %1 and all of its components?\nUninstallOnlyOnWin64=This installation can only be uninstalled on 64-bit Windows.\nOnlyAdminCanUninstall=This installation can only be uninstalled by a user with administrative privileges.\nUninstallStatusLabel=Please wait while %1 is removed from your computer.\nUninstalledAll=%1 was successfully removed from your computer.\nUninstalledMost=%1 uninstall complete.%n%nSome elements could not be removed. These can be removed manually.\nUninstalledAndNeedsRestart=To complete the uninstallation of %1, your computer must be restarted.%n%nWould you like to restart now?\nUninstallDataCorrupted=\"%1\" file is corrupted. Cannot uninstall\n\n; *** Uninstallation phase messages\nConfirmDeleteSharedFileTitle=Remove Shared File?\nConfirmDeleteSharedFile2=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.\nSharedFileNameLabel=File name:\nSharedFileLocationLabel=Location:\nWizardUninstalling=Uninstall Status\nStatusUninstalling=Uninstalling %1...\n\n; *** Shutdown block reasons\nShutdownBlockReasonInstallingApp=Installing %1.\nShutdownBlockReasonUninstallingApp=Uninstalling %1.\n\n; The custom messages below aren't used by Setup itself, but if you make\n; use of them in your scripts, you'll want to translate them.\n\n[CustomMessages]\n\nNameAndVersion=%1 version %2\nAdditionalIcons=Additional shortcuts:\nCreateDesktopIcon=Create a &desktop shortcut\nCreateQuickLaunchIcon=Create a &Quick Launch shortcut\nProgramOnTheWeb=%1 on the Web\nUninstallProgram=Uninstall %1\nLaunchProgram=Launch %1\nAssocFileExtension=&Associate %1 with the %2 file extension\nAssocingFileExtension=Associating %1 with the %2 file extension...\nAutoStartProgramGroupDescription=Startup:\nAutoStartProgram=Automatically start %1\nAddonHostProgramNotFound=%1 could not be located in the folder you selected.%n%nDo you want to continue anyway?\n"
  },
  {
    "path": "assets/buildtools/ISPPBuiltins.iss",
    "content": "// Inno Setup Preprocessor\n//\n// Inno Setup (C) 1997-2020 Jordan Russell. All Rights Reserved.\n// Portions Copyright (C) 2000-2020 Martijn Laan. All Rights Reserved.\n// Portions Copyright (C) 2001-2004 Alex Yackimoff. All Rights Reserved.\n//\n// See the ISPP help file for more documentation of the functions defined by this file\n//\n#if defined(ISPP_INVOKED) && !defined(_BUILTINS_ISS_)\n//\n#if PREPROCVER < 0x01000000\n# error Inno Setup Preprocessor version is outdated\n#endif\n//\n#define _BUILTINS_ISS_\n//\n#ifdef __OPT_E__\n# define private EnableOptE\n# pragma option -e-\n#endif\n\n#ifndef __POPT_P__\n# define private DisablePOptP\n#else\n# pragma parseroption -p-\n#endif\n\n#define NewLine            \"\\n\"\n#define Tab                \"\\t\"\n\n#pragma parseroption -p+\n\n#pragma spansymbol \"\\\"\n\n#define True               1\n#define False              0\n#define Yes                True\n#define No\t\t\t\t\t\t\t\t False\n\n#define MaxInt             0x7FFFFFFFFFFFFFFFL\n#define MinInt             0x8000000000000000L\n\n#define NULL\n#define void\n\n// TypeOf constants\n\n#define TYPE_ERROR         0\n#define TYPE_NULL          1\n#define TYPE_INTEGER       2\n#define TYPE_STRING        3\n#define TYPE_MACRO         4\n#define TYPE_FUNC          5\n#define TYPE_ARRAY         6\n\n// Helper macro to find out the type of an array element or expression. TypeOf\n// standard function only allows identifier as its parameter. Use this macro\n// to convert an expression to identifier.\n\n#define TypeOf2(any Expr) TypeOf(Expr)\n\n// ReadReg constants\n\n#define HKEY_CLASSES_ROOT  0x80000000UL\n#define HKEY_CURRENT_USER  0x80000001UL\n#define HKEY_LOCAL_MACHINE 0x80000002UL\n#define HKEY_USERS         0x80000003UL\n#define HKEY_CURRENT_CONFIG     0x80000005UL\n#define HKEY_CLASSES_ROOT_64    0x82000000UL\n#define HKEY_CURRENT_USER_64    0x82000001UL\n#define HKEY_LOCAL_MACHINE_64   0x82000002UL\n#define HKEY_USERS_64           0x82000003UL\n#define HKEY_CURRENT_CONFIG_64  0x82000005UL\n\n#define HKCR               HKEY_CLASSES_ROOT\n#define HKCU               HKEY_CURRENT_USER\n#define HKLM               HKEY_LOCAL_MACHINE\n#define HKU                HKEY_USERS\n#define HKCC               HKEY_CURRENT_CONFIG\n#define HKCR64             HKEY_CLASSES_ROOT_64\n#define HKCU64             HKEY_CURRENT_USER_64\n#define HKLM64             HKEY_LOCAL_MACHINE_64\n#define HKU64              HKEY_USERS_64\n#define HKCC64             HKEY_CURRENT_CONFIG_64\n\n// Exec constants\n\n#define SW_HIDE            0\n#define SW_SHOWNORMAL      1\n#define SW_NORMAL          1\n#define SW_SHOWMINIMIZED   2\n#define SW_SHOWMAXIMIZED   3\n#define SW_MAXIMIZE        3\n#define SW_SHOWNOACTIVATE  4\n#define SW_SHOW            5\n#define SW_MINIMIZE        6\n#define SW_SHOWMINNOACTIVE 7\n#define SW_SHOWNA          8\n#define SW_RESTORE         9\n#define SW_SHOWDEFAULT     10\n#define SW_MAX             10\n\n// Find constants\n\n#define FIND_MATCH         0x00\n#define FIND_BEGINS        0x01\n#define FIND_ENDS          0x02\n#define FIND_CONTAINS      0x03\n#define FIND_CASESENSITIVE 0x04 \n#define FIND_SENSITIVE     FIND_CASESENSITIVE\n#define FIND_AND           0x00\n#define FIND_OR            0x08\n#define FIND_NOT           0x10\n#define FIND_TRIM          0x20\n\n// FindFirst constants\n\n#define faReadOnly         0x00000001\n#define faHidden           0x00000002\n#define faSysFile          0x00000004\n#define faVolumeID         0x00000008\n#define faDirectory        0x00000010\n#define faArchive          0x00000020\n#define faSymLink          0x00000040\n#define faAnyFile          0x0000003F\n\n// GetStringFileInfo standard names\n\n#define COMPANY_NAME       \"CompanyName\"\n#define FILE_DESCRIPTION   \"FileDescription\"\n#define FILE_VERSION       \"FileVersion\"\n#define INTERNAL_NAME      \"InternalName\"\n#define LEGAL_COPYRIGHT    \"LegalCopyright\"\n#define ORIGINAL_FILENAME  \"OriginalFilename\"\n#define PRODUCT_NAME       \"ProductName\"\n#define PRODUCT_VERSION    \"ProductVersion\"\n\n// GetStringFileInfo helpers\n\n#define GetFileCompany(str FileName) GetStringFileInfo(FileName, COMPANY_NAME)\n#define GetFileDescription(str FileName) GetStringFileInfo(FileName, FILE_DESCRIPTION)\n#define GetFileVersionString(str FileName) GetStringFileInfo(FileName, FILE_VERSION)\n#define GetFileCopyright(str FileName) GetStringFileInfo(FileName, LEGAL_COPYRIGHT)\n#define GetFileOriginalFilename(str FileName) GetStringFileInfo(FileName, ORIGINAL_FILENAME)\n#define GetFileProductVersion(str FileName) GetStringFileInfo(FileName, PRODUCT_VERSION)\n\n#define DeleteToFirstPeriod(str *S) \\\n  Local[1] = Copy(S, 1, (Local[0] = Pos(\".\", S)) - 1), \\\n  S = Copy(S, Local[0] + 1), \\\n  Local[1]\n\n#define GetVersionComponents(str FileName, *Major, *Minor, *Rev, *Build) \\\n  Local[1]  = Local[0] = GetVersionNumbersString(FileName), \\\n  Local[1] == \"\" ? \"\" : ( \\\n    Major   = Int(DeleteToFirstPeriod(Local[1])), \\\n    Minor   = Int(DeleteToFirstPeriod(Local[1])), \\\n    Rev     = Int(DeleteToFirstPeriod(Local[1])), \\\n    Build   = Int(Local[1]), \\\n  Local[0])\n\n#define GetPackedVersion(str FileName, *Version) \\\n  Local[0] = GetVersionComponents(FileName, Local[1], Local[2], Local[3], Local[4]), \\\n  Version = PackVersionComponents(Local[1], Local[2], Local[3], Local[4]), \\\n  Local[0]\n\n#define GetVersionNumbers(str FileName, *MS, *LS) \\\n  Local[0] = GetPackedVersion(FileName, Local[1]), \\\n  UnpackVersionNumbers(Local[1], MS, LS), \\\n  Local[0]\n\n#define PackVersionNumbers(int VersionMS, int VersionLS) \\\n  VersionMS << 32 | (VersionLS & 0xFFFFFFFF)\n\n#define PackVersionComponents(int Major, int Minor, int Rev, int Build) \\\n  Major << 48 | (Minor & 0xFFFF) << 32 | (Rev & 0xFFFF) << 16 | (Build & 0xFFFF)\n\n#define UnpackVersionNumbers(int Version, *VersionMS, *VersionLS) \\\n  VersionMS = Version >> 32, \\\n  VersionLS = Version & 0xFFFFFFFF, \\\n  void\n\n#define UnpackVersionComponents(int Version, *Major, *Minor, *Rev, *Build) \\\n  Major = Version >> 48, \\\n  Minor = (Version >> 32) & 0xFFFF, \\\n  Rev   = (Version >> 16) & 0xFFFF, \\\n  Build = Version & 0xFFFF, \\\n  void\n\n#define VersionToStr(int Version) \\\n  Str(Version >> 48 & 0xFFFF) + \".\" + Str(Version >> 32 & 0xFFFF) + \".\" + \\\n  Str(Version >> 16 & 0xFFFF) + \".\" + Str(Version & 0xFFFF)\n\n#define EncodeVer(int Major, int Minor, int Revision = 0, int Build = -1) \\\n  (Major & 0xFF) << 24 | (Minor & 0xFF) << 16 | (Revision & 0xFF) << 8 | (Build >= 0 ? Build & 0xFF : 0)\n\n#define DecodeVer(int Version, int Digits = 3) \\\n  Str(Version >> 24 & 0xFF) + (Digits > 1 ? \".\" : \"\") + \\\n  (Digits > 1 ? \\\n    Str(Version >> 16 & 0xFF) + (Digits > 2 ? \".\" : \"\") : \"\") + \\\n  (Digits > 2 ? \\\n    Str(Version >> 8 & 0xFF) + (Digits > 3 && (Local = Version & 0xFF) ? \".\" : \"\") : \"\") + \\\n  (Digits > 3 && Local ? \\\n    Str(Version & 0xFF) : \"\")\n\n#define FindSection(str Section = \"Files\") \\\n  Find(0, \"[\" + Section + \"]\", FIND_MATCH | FIND_TRIM) + 1\n\n#if VER >= 0x03000000\n# define FindNextSection(int Line) \\\n    Find(Line, \"[\", FIND_BEGINS | FIND_TRIM, \"]\", FIND_ENDS | FIND_AND)\n# define FindSectionEnd(str Section = \"Files\") \\\n    FindNextSection(FindSection(Section))\n#else\n# define FindSectionEnd(str Section = \"Files\") \\\n    FindSection(Section) + EntryCount(Section)\n#endif\n\n#define FindCode() \\\n    Local[1] = FindSection(\"Code\"), \\\n    Local[0] = Find(Local[1] - 1, \"program\", FIND_BEGINS, \";\", FIND_ENDS | FIND_AND), \\\n    (Local[0] < 0 ? Local[1] : Local[0] + 1)\n\n#define ExtractFilePath(str PathName) \\\n  (Local[0] = \\\n    !(Local[1] = RPos(\"\\\", PathName)) ? \\\n      \"\" : \\\n      Copy(PathName, 1, Local[1] - 1)), \\\n  Local[0] + \\\n    ((Local[2] = Len(Local[0])) == 2 && Copy(Local[0], Local[2]) == \":\" ? \\\n      \"\\\" : \\\n      \"\")\n\n#define ExtractFileDir(str PathName) \\\n  RemoveBackslash(ExtractFilePath(PathName))\n\n#define ExtractFileExt(str PathName) \\\n  Local[0] = RPos(\".\", PathName), \\\n  Copy(PathName, Local[0] + 1)\n\n#define ExtractFileName(str PathName) \\\n  !(Local[0] = RPos(\"\\\", PathName)) ? \\\n    PathName : \\\n    Copy(PathName, Local[0] + 1)\n\n#define ChangeFileExt(str FileName, str NewExt) \\\n  !(Local[0] = RPos(\".\", FileName)) ? \\\n    FileName + \".\" + NewExt : \\\n    Copy(FileName, 1, Local[0]) + NewExt\n\n#define RemoveFileExt(str FileName) \\\n  !(Local[0] = RPos(\".\", FileName)) ? \\\n  FileName : \\\n  Copy(FileName, 1, Local[0] - 1)\n\n#define AddBackslash(str S) \\\n  Copy(S, Len(S)) == \"\\\" ? S : S + \"\\\"\n\n#define RemoveBackslash(str S) \\\n  Local[0] = Len(S), \\\n  Local[0] > 0 ? \\\n    Copy(S, Local[0]) == \"\\\" ? \\\n      (Local[0] == 3 && Copy(S, 2, 1) == \":\" ? \\\n        S : \\\n        Copy(S, 1, Local[0] - 1)) : \\\n      S : \\\n    \"\"\n\n#define Delete(str *S, int Index, int Count = MaxInt) \\\n  S = Copy(S, 1, Index - 1) + Copy(S, Index + Count)\n\n#define Insert(str *S, int Index, str Substr) \\\n  Index > Len(S) + 1 ? \\\n    S : \\\n    S = Copy(S, 1, Index - 1) + SubStr + Copy(S, Index)\n\n#define YesNo(str S) \\\n  (S = LowerCase(S)) == \"yes\" || S == \"true\" || S == \"1\"\n\n#define IsDirSet(str SetupDirective) \\\n  YesNo(SetupSetting(SetupDirective))\n\n#define Power(int X, int P = 2) \\\n  !P ? 1 : X * Power(X, P - 1)\n\n#define Min(int A, int B, int C = MaxInt)  \\\n  A < B ? A < C ? Int(A) : Int(C) : Int(B)\n\n#define Max(int A, int B, int C = MinInt)  \\\n  A > B ? A > C ? Int(A) : Int(C) : Int(B)\n\n#define SameText(str S1, str S2) \\\n  LowerCase(S1) == LowerCase(S2)\n\n#define SameStr(str S1, str S2) \\\n  S1 == S2\n\n#define WarnRenamedVersion(str OldName, str NewName) \\\n  Warning(\"Function \"\"\" + OldName + \"\"\" has been renamed. Use \"\"\" + NewName + \"\"\" instead.\")\n\n#define ParseVersion(str FileName, *Major, *Minor, *Rev, *Build) \\\n  WarnRenamedVersion(\"ParseVersion\", \"GetVersionComponents\"), \\\n  GetVersionComponents(FileName, Major, Minor, Rev, Build)\n\n#define GetFileVersion(str FileName) \\\n  WarnRenamedVersion(\"GetFileVersion\", \"GetVersionNumbersString\"), \\\n  GetVersionNumbersString(FileName)\n\n#ifdef DisablePOptP\n# pragma parseroption -p-\n#endif\n\n#ifdef EnableOptE\n# pragma option -e+\n#endif\n#endif"
  },
  {
    "path": "assets/elevate.cmd",
    "content": "@setlocal\n@echo off\n\n:: Try without elevation, in case %NVM_SYMLINK% is a user-owned path and the \n:: machine has Windows 10 Developer Mode enabled\n%*\nif %ERRORLEVEL% LSS 1 goto :EOF\n\n:: The command failed without elevation, try with elevation\nset CMD=%*\nset APP=%1\nstart wscript //nologo \"%~dpn0.vbs\" %*\n"
  },
  {
    "path": "assets/elevate.vbs",
    "content": "Set Shell = CreateObject(\"Shell.Application\")\nSet WShell = WScript.CreateObject(\"WScript.Shell\")\nSet ProcEnv = WShell.Environment(\"PROCESS\")\n\ncmd = ProcEnv(\"CMD\")\napp = ProcEnv(\"APP\")\nargs= Right(cmd,(Len(cmd)-Len(app)))\n\nIf (WScript.Arguments.Count >= 1) Then\n  Shell.ShellExecute app, args, \"\", \"runas\", 0\nElse\n  WScript.Quit\nEnd If\n"
  },
  {
    "path": "assets/install.cmd",
    "content": "@echo off\nset /P NVM_PATH=\"Enter the absolute path where the nvm-windows zip file is extracted/copied to: \"\nset NVM_HOME=%NVM_PATH%\nset NVM_SYMLINK=C:\\Program Files\\nodejs\nsetx /M NVM_HOME \"%NVM_HOME%\"\nsetx /M NVM_SYMLINK \"%NVM_SYMLINK%\"\n\necho PATH=%PATH% > %NVM_HOME%\\PATH.txt\n\nfor /f \"skip=2 tokens=2,*\" %%A in ('reg query \"HKLM\\System\\CurrentControlSet\\Control\\Session Manager\\Environment\" /v Path 2^>nul') do (\n  setx /M PATH \"%%B;%%NVM_HOME%%;%%NVM_SYMLINK%%\"\n)\n\nif exist \"%SYSTEMDRIVE%\\Program Files (x86)\\\" (\nset SYS_ARCH=64\n) else (\nset SYS_ARCH=32\n)\n(echo root: %NVM_HOME% && echo path: %NVM_SYMLINK% && echo arch: %SYS_ARCH% && echo proxy: none) > %NVM_HOME%\\settings.txt\n\nnotepad %NVM_HOME%\\settings.txt\n@echo on\n"
  },
  {
    "path": "assets/run.cmd",
    "content": "@echo off\npowershell -NoProfile -WindowStyle Hidden -Command \"Start-Process -Filepath 'C:\\Users\\corey\\OneDrive\\Documents\\workspace\\libraries\\oss\\coreybutler\\nvm-windows\\bin\\nvm.exe' -ArgumentList '\\\"%*\\\"' -NoNewWindow\""
  },
  {
    "path": "assets/setuserenv.vbs",
    "content": "Set WSHShell = CreateObject(\"WScript.Shell\")\nuserpath = WSHShell.RegRead(\"HKCU\\Environment\\Path\")\nIf InStr(userpath, \"%NVM_HOME%\") = False Then\n    userpath = userpath & \";%NVM_HOME%;\"\nEnd If\nIf InStr(userpath, \"%NVM_SYMLINK%\") = False Then\n    userpath = userpath & \";%NVM_SYMLINK%;\"\nEnd If\n\nuserpath = Replace(userpath, \";;\", \";\")\nWSHShell.RegWrite \"HKCU\\Environment\\Path\", userpath"
  },
  {
    "path": "assets/unsetuserenv.vbs",
    "content": "Set WSHShell = CreateObject(\"WScript.Shell\")\nuserpath = WSHShell.RegRead(\"HKCU\\Environment\\Path\")\nuserpath = Replace(userpath, \"%NVM_HOME%\", \"\")\nuserpath = Replace(userpath, \"%NVM_SYMLINK%\", \"\")\nuserpath = Replace(userpath, \";;\", \";\")\nWSHShell.RegWrite \"HKCU\\Environment\\Path\", userpath"
  },
  {
    "path": "build.js",
    "content": "// This is a deno helper script to locally build the installer using Inno Setup\n// Yeah, I know  it's not Node, but we need to compile this and Node SEAs on Win32 are a PITA.\nconst content = await Deno.readTextFile('./nvm.iss')\nconst data = JSON.parse(await Deno.readTextFile('./src/manifest.json'))\nconst {version} = data\nconst output = content.replaceAll('{{VERSION}}', version)\nawait Deno.writeTextFile('./.tmp.iss', output)\n\nconsole.log('Viewing /.tmp.iss')\noutput.split(\"\\n\").forEach((line, num) => {\n  let n = `${num+1}`\n  while (n.length < 3) {\n    n = ' ' + n\n  }\n\n  console.log(`${n} | ${line}`)\n})\n\nconst command = await new Deno.Command('.\\\\assets\\\\buildtools\\\\iscc.exe', {\n  args: ['.\\\\.tmp.iss'],\n  stdout: 'piped',\n  stderr: 'piped',\n})\n\nconst process = command.spawn();\n\n// Stream stdout\n(async () => {\n  const decoder = new TextDecoder();\n  for await (const chunk of process.stdout) {\n    console.log(decoder.decode(chunk));\n  }\n})();\n\n// Stream stderr\n(async () => {\n  const decoder = new TextDecoder();\n  for await (const chunk of process.stderr) {\n    console.error(decoder.decode(chunk));\n  }\n})();\n\n// Wait for completion\nconst status = await process.status;\nDeno.remove('.\\\\.tmp.iss');\nif (!status.success) {\n  Deno.exit(status.code);\n}\n"
  },
  {
    "path": "examples/settings.txt",
    "content": "root: C:\\Users\\Corey\\AppData\\Roaming\\nvm\npath: C:\\Program Files\\nodejs\narch: 64\nproxy: none\n"
  },
  {
    "path": "nvm.iss",
    "content": "#define MyAppName \"NVM for Windows\"\n#define MyAppShortName \"nvm\"\n#define MyAppLCShortName \"nvm\"\n#define MyAppVersion \"{{VERSION}}\"\n#define MyAppPublisher \"Author Software Inc.\"\n#define MyAppURL \"https://github.com/coreybutler/nvm-windows\"\n#define MyAppExeName \"nvm.exe\"\n#define MyIcon \"bin\\nvm.ico\"\n#define MyAppId \"40078385-F676-4C61-9A9C-F9028599D6D3\"\n#define ProjectRoot \".\"\n\n[Setup]\n; NOTE: The value of AppId uniquely identifies this application.\n; Do not use the same AppId value in installers for other applications.\nPrivilegesRequired=admin\nSignTool=signtool\nSignedUninstaller=yes\nAppId={#MyAppId}\nAppName={#MyAppName}\nAppVersion={#MyAppVersion}\nAppCopyright=Copyright (C) 2018-{code:GetCurrentYear} Author Software Inc., Ecor Ventures LLC, Corey Butler, and contributors.\nAppVerName={#MyAppName} {#MyAppVersion}\nAppPublisher={#MyAppPublisher}\nAppPublisherURL={#MyAppURL}\nAppSupportURL={#MyAppURL}\nAppUpdatesURL={#MyAppURL}\nDefaultDirName={localappdata}\\{#MyAppShortName}\nDisableDirPage=no\nDefaultGroupName={#MyAppName}\nAllowNoIcons=yes\nLicenseFile={#ProjectRoot}\\LICENSE\nOutputDir={#ProjectRoot}\\dist\\{#MyAppVersion}\nOutputBaseFilename={#MyAppLCShortName}-setup\nSetupIconFile={#ProjectRoot}\\{#MyIcon}\nCompression=lzma\nSolidCompression=yes\nChangesEnvironment=yes\nDisableProgramGroupPage=yes\nArchitecturesInstallIn64BitMode=x64\nUninstallDisplayIcon={app}\\{#MyIcon}\n\n; Version information\nVersionInfoVersion={{VERSION}}.0\nVersionInfoCopyright=Copyright © {code:GetCurrentYear} Author Software Inc., Ecor Ventures LLC, Corey Butler, and contributors.\nVersionInfoCompany=Author Software Inc.\nVersionInfoDescription=Node.js version manager for Windows\nVersionInfoProductName={#MyAppShortName}\nVersionInfoProductTextVersion={#MyAppVersion}\n\n[Languages]\nName: \"english\"; MessagesFile: \"compiler:Default.isl\"\n\n[Tasks]\nName: \"quicklaunchicon\"; Description: \"{cm:CreateQuickLaunchIcon}\"; GroupDescription: \"{cm:AdditionalIcons}\"; Flags: unchecked; OnlyBelowVersion: 0,6.1\n\n[Files]\nSource: \"{#ProjectRoot}\\bin\\*\"; DestDir: \"{app}\"; BeforeInstall: PreInstall; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: \"{#ProjectRoot}\\bin\\install.cmd\"\n\n[Icons]\nName: \"{group}\\{#MyAppShortName}\"; Filename: \"{app}\\{#MyAppExeName}\"; IconFilename: \"{#MyIcon}\"\nName: \"{group}\\Uninstall {#MyAppShortName}\"; Filename: \"{uninstallexe}\"\n\n[Registry]\n; Register the URL protocol 'nvm'\nRoot: HKCR; Subkey: \"{#MyAppShortName}\"; ValueType: string; ValueName: \"\"; ValueData: \"URL:nvm\"; Flags: uninsdeletekey\nRoot: HKCR; Subkey: \"{#MyAppShortName}\"; ValueType: string; ValueName: \"URL Protocol\"; ValueData: \"\"; Flags: uninsdeletekey\nRoot: HKCR; Subkey: \"{#MyAppShortName}\\DefaultIcon\"; ValueType: string; ValueName: \"\"; ValueData: \"{app}\\{#MyAppExeName},0\"; Flags: uninsdeletekey\nRoot: HKCR; Subkey: \"{#MyAppShortName}\\shell\\launch\\command\"; ValueType: string; ValueName: \"\"; ValueData: \"\"\"{app}\\{#MyAppExeName}\"\" \"\"%1\"\"\"; Flags: uninsdeletekey\n\n[Code]\nvar\n  SymlinkPage: TInputDirWizardPage;\n  NotificationOptionPage: TInputOptionWizardPage;\n  EmailPage: TWizardPage;\n  EmailEdit: TEdit;\n  EmailLabel: TLabel;\n  PreText: TLabel;\n\nfunction GetCurrentYear(Param: String): String;\nbegin\n  result := GetDateTimeString('yyyy', '-', ':');\nend;\n\nfunction IsDirEmpty(dir: string): Boolean;\nvar\n  FindRec: TFindRec;\n  ct: Integer;\nbegin\n  ct := 0;\n  if FindFirst(ExpandConstant(dir + '\\*'), FindRec) then\n  try\n    repeat\n      if FindRec.Attributes and FILE_ATTRIBUTE_DIRECTORY = 0 then\n        ct := ct+1;\n    until\n      not FindNext(FindRec);\n  finally\n    FindClose(FindRec);\n    Result := ct = 0;\n  end;\nend;\n\n//function getInstalledVersions(dir: string):\nvar\n  nodeInUse: string;\n\nprocedure TakeControl(np: string; nv: string);\nvar\n  path: string;\nbegin\n  // Move the existing node.js installation directory to the nvm root & update the path\n  RenameFile(np,ExpandConstant('{app}')+'\\'+nv);\n\n  RegQueryStringValue(HKEY_LOCAL_MACHINE,\n    'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment',\n    'Path', path);\n\n  StringChangeEx(path,np+'\\','',True);\n  StringChangeEx(path,np,'',True);\n  StringChangeEx(path,np+';;',';',True);\n\n  RegWriteExpandStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment', 'Path', path);\n\n  RegQueryStringValue(HKEY_CURRENT_USER,\n    'Environment',\n    'Path', path);\n\n  StringChangeEx(path,np+'\\','',True);\n  StringChangeEx(path,np,'',True);\n  StringChangeEx(path,np+';;',';',True);\n\n  RegWriteExpandStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', path);\n\n  nodeInUse := ExpandConstant('{app}')+'\\'+nv;\n\nend;\n\nfunction Ansi2String(AString:AnsiString):String;\nvar\n i : Integer;\n iChar : Integer;\n outString : String;\nbegin\n outString :='';\n for i := 1 to Length(AString) do\n begin\n  iChar := Ord(AString[i]); //get int value\n  outString := outString + Chr(iChar);\n end;\n\n Result := outString;\nend;\n\nprocedure PreInstall();\nvar\n  TmpResultFile, TmpJS, NodeVersion, NodePath: string;\n  stdout: Ansistring;\n  ResultCode: integer;\n  msg1, msg2, msg3, dir1: Boolean;\nbegin\n  // Create a file to check for Node.JS\n  TmpJS := ExpandConstant('{tmp}') + '\\nvm_check.js';\n  SaveStringToFile(TmpJS, 'console.log(require(\"path\").dirname(process.execPath));', False);\n\n  // Execute the node file and save the output temporarily\n  TmpResultFile := ExpandConstant('{tmp}') + '\\nvm_node_check.txt';\n  Exec(ExpandConstant('{cmd}'), '/C node \"'+TmpJS+'\" > \"' + TmpResultFile + '\"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);\n  DeleteFile(TmpJS)\n\n  // Process the results\n  LoadStringFromFile(TmpResultFile,stdout);\n  NodePath := Trim(Ansi2String(stdout));\n  if DirExists(NodePath) then begin\n    Exec(ExpandConstant('{cmd}'), '/C node -v > \"' + TmpResultFile + '\"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);\n    LoadStringFromFile(TmpResultFile, stdout);\n    NodeVersion := Trim(Ansi2String(stdout));\n    msg1 := SuppressibleMsgBox('Node '+NodeVersion+' is already installed. Do you want NVM to control this version?', mbConfirmation, MB_YESNO, IDYES) = IDNO;\n    if msg1 then begin\n      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;\n      if msg2 then begin\n        TakeControl(NodePath, NodeVersion);\n      end;\n      if not msg2 then begin\n        DeleteFile(TmpResultFile);\n        WizardForm.Close;\n      end;\n    end;\n    if not msg1 then\n    begin\n      TakeControl(NodePath, NodeVersion);\n    end;\n  end;\n\n  // Make sure the symlink directory doesn't exist\n  if DirExists(SymlinkPage.Values[0]) then begin\n    // If the directory is empty, just delete it since it will be recreated anyway.\n    dir1 := IsDirEmpty(SymlinkPage.Values[0]);\n    if dir1 then begin\n      RemoveDir(SymlinkPage.Values[0]);\n    end;\n    if not dir1 then begin\n      msg3 := SuppressibleMsgBox(SymlinkPage.Values[0]+' will be overwritten and all contents will be lost. Do you want to proceed?', mbConfirmation, MB_OKCANCEL, IDOK) = IDOK;\n      if msg3 then begin\n        RemoveDir(SymlinkPage.Values[0]);\n      end;\n      if not msg3 then begin\n        //RaiseException('The symlink cannot be created due to a conflict with the existing directory at '+SymlinkPage.Values[0]);\n        WizardForm.Close;\n      end;\n    end;\n  end;\nend;\n\nfunction IsSymbolicLink(const Path: string): Boolean;\nvar\n  FindRec: TFindRec;\nbegin\n  Result := False;\n  if FindFirst(Path, FindRec) then\n  begin\n    Result := (FindRec.Attributes and FILE_ATTRIBUTE_REPARSE_POINT) <> 0;\n    FindClose(FindRec);\n  end;\nend;\n\nprocedure SymlinkPageChange(Sender: TObject);\nvar\n  NewPath: string;\nbegin\n  // Append '\\nodejs' to the path if it is not already appended\n  NewPath := AddBackslash(SymlinkPage.Values[0]) + 'nodejs';\n  if Copy(SymlinkPage.Values[0], Length(SymlinkPage.Values[0]) - Length('\\nodejs') + 1, Length('\\nodejs')) <> '\\nodejs' then\n    SymlinkPage.Values[0] := NewPath;\n\n  // Check if the new path exists and is not a symbolic link\n  if DirExists(NewPath) and not IsSymbolicLink(NewPath) then\n  begin\n    MsgBox('The directory \"' + NewPath + '\" already exists as a physical directory. Please choose a different location.', mbError, MB_OK);\n    SymlinkPage.Values[0] := '';\n  end;\nend;\n\nprocedure InitializeWizard;\nbegin\n  SymlinkPage := CreateInputDirPage(wpSelectDir,\n    'Active Version Location',\n    'The active version of Node.js will always be available at this location.',\n    'Select the folder in which Setup should create the symlink, then click Next.',\n    False, '');\n  SymlinkPage.Add('This directory will automatically be added to your system path.');\n  SymlinkPage.Values[0] := ExpandConstant('C:\\nvm4w\\nodejs');\n\n  // Assign the OnChange event handler\n  SymlinkPage.Edits[0].OnChange := @SymlinkPageChange;\n\n  // Notification option page (after the Symlink page)\n  NotificationOptionPage := CreateInputOptionPage(\n    SymlinkPage.ID, // Ensures the Notification page appears right after the Symlink page\n    'Desktop Notifications (PREVIEW)',\n    'NVM for Windows supports the basic (free) edition of Author Notifications.',\n    'Select the events you wish to be notified of. Your choices can be modified at any time in the future.',\n    FALSE,\n    FALSE);\n\n  // Pre-checked checkbox\n  NotificationOptionPage.AddEx('Node.js LTS releases (Long-Term Support/Stable)', 0, FALSE);\n  NotificationOptionPage.AddEx('Node.js Current releases (Latest/Testing)', 0, FALSE);\n  NotificationOptionPage.AddEx('NVM For Windows releases', 0, FALSE);\n  NotificationOptionPage.AddEx('Author updates and releases (upcoming NVM for Windows successor)', 0, FALSE);\n  NotificationOptionPage.Values[0] := TRUE;\n  NotificationOptionPage.Values[1] := TRUE;\n  NotificationOptionPage.Values[2] := TRUE;\n  NotificationOptionPage.Values[3] := TRUE;\n\n  // Email Input Page\n  EmailPage := CreateCustomPage(\n    NotificationOptionPage.ID,\n    'Author Progress Email Signup',\n    'Get details about Author development milestones in your inbox.');\n\n  // Add introductory text above the input field\n  PreText := TLabel.Create(WizardForm);\n  PreText.Parent := EmailPage.Surface;\n  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. ' +\n                     'Leave it blank if you do not wish to receive notifications.';\n  PreText.Left := 10;\n  PreText.Top := 10;\n  PreText.Width := 600; // Adjust width to fit the text\n  PreText.WordWrap := True; // Ensures the text wraps if it exceeds the width\n\n  // Add a label for the email input field\n  EmailLabel := TLabel.Create(WizardForm);\n  EmailLabel.Parent := EmailPage.Surface;\n  EmailLabel.Caption := 'Email Address: (Optional)';\n  EmailLabel.Font.Style := [fsBold];\n  EmailLabel.Left := 10;  // Position from the left\n  EmailLabel.Top := 80;   // Position from the top\n\n  // Add an email input field on the EmailPage\n  EmailEdit := TEdit.Create(WizardForm);\n  EmailEdit.Parent := EmailPage.Surface;\n  EmailEdit.Left := 10;   // Align with the label\n  EmailEdit.Top := 110;    // Position just below the label\n  EmailEdit.Width := 610;\n  EmailEdit.Text := ''; // Default value\nend;\n\nfunction LastPos(const SubStr, S: string): Integer;\nvar\n  I: Integer;\nbegin\n  Result := 0;\n  for I := Length(S) downto 1 do\n  begin\n    if Copy(S, I, Length(SubStr)) = SubStr then\n    begin\n      Result := I;\n      Exit;\n    end;\n  end;\nend;\n\nfunction IsValidEmail(const Email: string): Boolean;\nvar\n  AtPos, DotPos: Integer;\nbegin\n  AtPos := Pos('@', Email);\n  DotPos := LastPos('.', Email);\n  Result := (AtPos > 1) and (DotPos > AtPos + 1) and (DotPos < Length(Email));\nend;\n\nfunction NextButtonClick(CurPageID: Integer): Boolean;\nvar\n  Email: string;\nbegin\n  Result := True; // Allow navigation by default\n\n  if CurPageID = SymlinkPage.ID then\n  begin\n    // Check if the directory is empty\n    if DirExists(SymlinkPage.Values[0]) then\n    begin\n      if IsDirEmpty(SymlinkPage.Values[0]) then\n      begin\n        // If the directory is empty, just delete it since it will be recreated anyway.\n        RemoveDir(SymlinkPage.Values[0]);\n      end\n      else\n      begin\n        // Show a warning if the directory is not empty\n        MsgBox('The selected directory is not empty. Please choose a different path.', mbError, MB_OK);\n        Result := False; // Prevent navigation to the next page\n      end;\n    end;\n  end;\n\n  if CurPageID = EmailPage.ID then\n  begin\n    Email := Trim(EmailEdit.Text); // Remove leading/trailing spaces\n    if (Email <> '') and not IsValidEmail(Email) then\n    begin\n      MsgBox('Please enter a valid email address or leave the field blank.', mbError, MB_OK);\n      Result := False; // Prevent navigation to the next page\n    end\n    else\n    begin\n      WizardForm.NextButton.Enabled := True; // Allow navigation to the next page\n    end;\n  end;\n\n  // Handle the Notification page logic\n  if CurPageID = NotificationOptionPage.ID then\n  begin\n    if NotificationOptionPage.Values[0] then\n    begin\n      Log('User opted to enable Node.js release notifications.');\n      // Add your logic for enabling notifications here\n    end\n    else\n    begin\n      Log('User opted out of Node.js release notifications.');\n    end;\n  end;\nend;\n\nfunction InitializeUninstall(): Boolean;\nvar\n  path: string;\n  nvm_symlink: string;\nbegin\n  SuppressibleMsgBox('Removing NVM for Windows will remove the nvm command and all versions of node.js, including global npm modules.', mbInformation, MB_OK, IDOK);\n\n  // Remove the symlink\n  RegQueryStringValue(HKEY_LOCAL_MACHINE,\n    'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment',\n    'NVM_SYMLINK', nvm_symlink);\n  RemoveDir(nvm_symlink);\n\n  // Clean the registry\n  RegDeleteValue(HKEY_LOCAL_MACHINE,\n    'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment',\n    'NVM_HOME')\n  RegDeleteValue(HKEY_LOCAL_MACHINE,\n    'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment',\n    'NVM_SYMLINK')\n  RegDeleteValue(HKEY_CURRENT_USER,\n    'Environment',\n    'NVM_HOME')\n  RegDeleteValue(HKEY_CURRENT_USER,\n    'Environment',\n    'NVM_SYMLINK')\n\n  RegQueryStringValue(HKEY_LOCAL_MACHINE,\n    'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment',\n    'Path', path);\n\n  StringChangeEx(path,'%NVM_HOME%','',True);\n  StringChangeEx(path,'%NVM_SYMLINK%','',True);\n  StringChangeEx(path,';;',';',True);\n\n  RegWriteExpandStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment', 'Path', path);\n\n  RegQueryStringValue(HKEY_CURRENT_USER,\n    'Environment',\n    'Path', path);\n\n  StringChangeEx(path,'%NVM_HOME%','',True);\n  StringChangeEx(path,'%NVM_SYMLINK%','',True);\n  StringChangeEx(path,';;',';',True);\n\n  RegWriteExpandStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', path);\n\n  Result := True;\nend;\n\n// Generate the settings file based on user input & update registry\nprocedure CurStepChanged(CurStep: TSetupStep);\nvar\n  path: string;\n  rootDir: string;\nbegin\n  if CurStep = ssInstall then\n  begin\n    // Ensure the root directory exists\n    rootDir := ExtractFileDir(SymlinkPage.Values[0]);\n    if not DirExists(rootDir) then\n    begin\n      if not CreateDir(rootDir) then\n      begin\n        MsgBox('Failed to create root directory: ' + rootDir, mbError, MB_OK);\n        WizardForm.Close;\n      end;\n    end;\n  end;\n\n  if CurStep = ssPostInstall then\n  begin\n    SaveStringToFile(ExpandConstant('{app}\\settings.txt'), 'root: ' + ExpandConstant('{app}') + #13#10 + 'path: ' + SymlinkPage.Values[0] + #13#10, False);\n\n    // Add Registry settings\n    RegWriteExpandStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment', 'NVM_HOME', ExpandConstant('{app}'));\n    RegWriteExpandStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment', 'NVM_SYMLINK', SymlinkPage.Values[0]);\n    RegWriteExpandStringValue(HKEY_CURRENT_USER, 'Environment', 'NVM_HOME', ExpandConstant('{app}'));\n    RegWriteExpandStringValue(HKEY_CURRENT_USER, 'Environment', 'NVM_SYMLINK', SymlinkPage.Values[0]);\n\n    RegWriteStringValue(HKEY_LOCAL_MACHINE, 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{#MyAppId}_is1', 'DisplayVersion', '{#MyAppVersion}');\n\n    // Update system and user PATH if needed\n    RegQueryStringValue(HKEY_LOCAL_MACHINE,\n      'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment',\n      'Path', path);\n    if Pos('%NVM_HOME%',path) = 0 then begin\n      path := path+';%NVM_HOME%';\n      StringChangeEx(path,';;',';',True);\n      RegWriteExpandStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment', 'Path', path);\n    end;\n    if Pos('%NVM_SYMLINK%',path) = 0 then begin\n      path := path+';%NVM_SYMLINK%';\n      StringChangeEx(path,';;',';',True);\n      RegWriteExpandStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment', 'Path', path);\n    end;\n     RegQueryStringValue(HKEY_CURRENT_USER,\n      'Environment',\n      'Path', path);\n    if Pos('%NVM_HOME%',path) = 0 then begin\n      path := path+';%NVM_HOME%';\n      StringChangeEx(path,';;',';',True);\n      RegWriteExpandStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', path);\n    end;\n    if Pos('%NVM_SYMLINK%',path) = 0 then begin\n      path := path+';%NVM_SYMLINK%';\n      StringChangeEx(path,';;',';',True);\n      RegWriteExpandStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', path);\n    end;\n  end;\n\nend;\n\nfunction GetNotificationString(Param: String): String;\nbegin\n  Result := ' subscribe';\n  if NotificationOptionPage.Values[0] then\n  begin\n    Result := Result + ' --lts';\n  end;\n  if NotificationOptionPage.Values[1] then\n  begin\n    Result := Result + ' --current';\n  end;\n  if NotificationOptionPage.Values[2] then\n  begin\n    Result := Result + ' --nvm4w';\n  end;\n  if NotificationOptionPage.Values[3] then\n  begin\n    Result := Result + ' --author';\n  end;\n  Result := Trim(Result);\nend;\n\nfunction getSymLink(o: string): string;\nbegin\n  Result := SymlinkPage.Values[0];\nend;\n\nfunction getCurrentVersion(o: string): string;\nbegin\n  Result := nodeInUse;\nend;\n\nfunction isNodeAlreadyInUse(): boolean;\nbegin\n  Result := Length(nodeInUse) > 0;\nend;\n\nfunction isEmailSupplied(): boolean;\nbegin\n  Result := Trim(EmailEdit.Text) <> '';\nend;\n\nfunction GetEmailRegistrationString(Param: String): string;\nbegin\n  Result := ' author newsletter --notify ' + Trim(EmailEdit.Text);\nend;\n\n[Run]\nFilename: \"{app}\\nvm.exe\"; Parameters: \"{code:GetNotificationString}\"; Flags: waituntilidle runhidden;\nFilename: \"{app}\\nvm.exe\"; Parameters: \"{code:GetEmailRegistrationString}\"; Check: isEmailSupplied; Flags: waituntilidle runhidden;\nFilename: \"{cmd}\"; Parameters: \"/C \"\"mklink /D \"\"{code:getSymLink}\"\" \"\"{code:getCurrentVersion}\"\"\"\" \"; Check: isNodeAlreadyInUse; Flags: waituntilidle runhidden;\nFilename: \"powershell.exe\"; Parameters: \"-NoExit -Command refreshenv; cls; Write-Host 'Welcome to NVM for Windows v{{VERSION}}'\"; Description: \"Open with Powershell\"; Flags: postinstall skipifsilent;\n\n[UninstallRun]\nFilename: \"{app}\\nvm.exe\"; Parameters: \"unsubscribe --lts --current --nvm4w --author\"; Flags: runhidden; RunOnceId: \"UnregisterNVMForWindows\";\nFilename: \"{app}\\nvm.exe\"; Parameters: \"off\"; Flags: runhidden; RunOnceId: \"RemoveNVMForWindowsSymlink\";\nFilename: \"{cmd}\"; Parameters: \"/C rmdir /S /Q \"\"{code:getSymLink}\"\"\"; Flags: runhidden; RunOnceId: \"RemoveSymlink\";\n\n[UninstallDelete]\nType: filesandordirs; Name: \"{app}\";\nType: filesandordirs; Name: \"{localappdata}\\.nvm\";\n"
  },
  {
    "path": "src/arch/arch.go",
    "content": "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( path string, match string, limit int) bool {\n  // Transform to byte array the string\n  toMatch, err := hex.DecodeString(match);\n  if (err != nil) {\n    return false;\n  }\n  \n  // Opening the file and checking if there is an error\n  file, err := os.Open(path)\n  if err != nil {\n    return false;\n  }\n\n  // Close file upon return\n  defer file.Close()\n\n  // Allocate 1 byte array to perform the match\n  bit := make([]byte, 1);\n  j := 0\n  for i := 0; i < limit; i++ {\n    file.Read(bit);\n\n    if bit[0] != toMatch[j] {\n      j = 0;\n    }\n    if bit[0] == toMatch[j] {\n      j++;\n      if (j >= len(toMatch)) {\n        file.Close();\n        return true;\n      }\n    }\n  }\n  file.Close();\n  return false;\n}\n\nfunc Bit(path string) string {\n  isarm64 := SearchBytesInFile(path, \"5045000064AA\", 400)\n  is64 := SearchBytesInFile(path, \"504500006486\", 400);\n  is32 := SearchBytesInFile(path, \"504500004C\", 400);\n  if isarm64 {\n\treturn \"arm64\";\n  } else if is64 {\n    return \"64\";\n  } else if is32 {\n    return \"32\";\n  }\n  return \"?\";\n}\n\nfunc Validate(str string) (string){\n  if str == \"\" {\n    str = strings.ToLower(os.Getenv(\"PROCESSOR_ARCHITECTURE\"))\n  }\n  if strings.Contains(str, \"arm64\") {\n\t  return \"arm64\"\n  }\n  if strings.Contains(str, \"64\") {\n    return \"64\"\n  } \n  return \"32\"\n}\n"
  },
  {
    "path": "src/author/bridge.go",
    "content": "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/coreybutler/go-fsutil\"\n\t\"golang.org/x/sys/windows\"\n)\n\nconst (\n\t// Constants for SetWindowPos\n\tSWP_NOMOVE     = 0x0002\n\tSWP_NOZORDER   = 0x0004\n\tSWP_SHOWWINDOW = 0x0040\n\tSW_HIDE        = 0 // Hide the window (for the main console window)\n)\n\nvar (\n\tmodkernel32    = syscall.NewLazyDLL(\"kernel32.dll\")\n\tmoduser32      = syscall.NewLazyDLL(\"user32.dll\")\n\tprocGetConsole = modkernel32.NewProc(\"GetConsoleWindow\")\n\tprocShowWindow = moduser32.NewProc(\"ShowWindow\")\n)\n\n// Hide the main console window (the one running the Go app)\nfunc hideConsole() {\n\thwnd, _, _ := procGetConsole.Call()\n\tif hwnd != 0 {\n\t\tprocShowWindow.Call(hwnd, uintptr(SW_HIDE)) // Hide the main console window\n\t}\n}\n\nfunc Bridge(args ...string) {\n\texe, _ := os.Executable()\n\tbridge := filepath.Join(filepath.Dir(exe), \"author-nvm.exe\")\n\tif !fsutil.Exists(bridge) {\n\t\tfmt.Println(\"error: author bridge not found\")\n\t\tos.Exit(1)\n\t}\n\n\tif len(args) < 2 {\n\t\tif !(len(args) == 1 && args[0] == \"version\") {\n\t\t\tfmt.Printf(\"error: invalid number of arguments passed to author bridge: %d\\n\", len(args))\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\n\tcommand := args[0]\n\targs = args[1:]\n\n\t// fmt.Printf(\"running author bridge: %s %v\\n\", command, args)\n\n\thideConsole()\n\n\tcmd := exec.Command(bridge, append([]string{command}, args...)...)\n\tcmd.SysProcAttr = &windows.SysProcAttr{\n\t\tCreationFlags: windows.CREATE_NEW_PROCESS_GROUP | windows.DETACHED_PROCESS | windows.CREATE_NO_WINDOW,\n\t}\n\t// Create pipes for Stdout and Stderr\n\tstdoutPipe, _ := cmd.StdoutPipe()\n\tstderrPipe, _ := cmd.StderrPipe()\n\n\t// Start the command\n\tif err := cmd.Start(); err != nil {\n\t\tfmt.Println(\"error starting bridge command:\", err)\n\t\tos.Exit(1)\n\t}\n\n\t// Stream Stdout\n\tgo func() {\n\t\tscanner := bufio.NewScanner(stdoutPipe)\n\t\tfor scanner.Scan() {\n\t\t\tfmt.Println(scanner.Text())\n\t\t}\n\t}()\n\n\t// Stream Stderr\n\tgo func() {\n\t\tscanner := bufio.NewScanner(stderrPipe)\n\t\tfor scanner.Scan() {\n\t\t\tfmt.Println(scanner.Text())\n\t\t}\n\t}()\n\n\tif command == \"upgrade\" {\n\t\tfor _, arg := range args {\n\t\t\tif strings.Contains(arg, \"--rollback\") {\n\t\t\t\tfmt.Println(\"exiting to rollback nvm.exe...\")\n\t\t\t\ttime.Sleep(1 * time.Second)\n\t\t\t\tos.Exit(0)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Wait for the command to finish\n\tif err := cmd.Wait(); err != nil {\n\t\tfmt.Println(\"bridge command finished with error:\", err)\n\t}\n}\n"
  },
  {
    "path": "src/encoding/encoding.go",
    "content": "package encoding\n\nimport (\n\t\"strings\"\n\t\"unicode/utf8\"\n\n\t\"github.com/saintfish/chardet\"\n)\n\nfunc DetectCharset(content []byte) (string, error) {\n\tdetector := chardet.NewTextDetector()\n\tresult, err := detector.DetectBest(content)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn strings.ToUpper(result.Charset), nil\n}\n\nfunc ToUTF8(content string) []byte {\n\tb := make([]byte, len(content))\n\ti := 0\n\tfor _, r := range content {\n\t\ti += utf8.EncodeRune(b[i:], r)\n\t}\n\n\treturn b[:i]\n}\n\n// func ToUTF8(content []byte, ignoreInvalidITF8Chars ...bool) (string, error) {\n// \tignore := false\n// \tif len(ignoreInvalidITF8Chars) > 0 {\n// \t\tignore = ignoreInvalidITF8Chars[0]\n// \t}\n\n// \tcs, err := DetectCharset(content)\n// \tif err != nil {\n// \t\tif !ignore {\n// \t\t\treturn \"\", err\n// \t\t}\n// \t\tcs = \"UTF-8\"\n// \t}\n\n// \tbs := string(content)\n// \tif ignore {\n// \t\tif !utf8.ValidString(bs) {\n// \t\t\tv := make([]rune, 0, len(bs))\n// \t\t\tfor i, r := range bs {\n// \t\t\t\tif r == utf8.RuneError {\n// \t\t\t\t\t_, size := utf8.DecodeRuneInString(bs[i:])\n// \t\t\t\t\tif size == 1 {\n// \t\t\t\t\t\tcontinue\n// \t\t\t\t\t}\n// \t\t\t\t}\n// \t\t\t\tv = append(v, r)\n// \t\t\t}\n// \t\t\tbs = string(v)\n// \t\t}\n// \t}\n\n// \tif cs == \"UTF-8\" {\n// \t\treturn bs, nil\n// \t}\n\n// \tconverter, err := iconv.NewConverter(cs, \"UTF-8\")\n// \tif err != nil {\n// \t\terr = errors.New(\"Failed to convert \" + cs + \" to UTF-8: \" + err.Error())\n// \t\treturn bs, err\n// \t}\n\n// \treturn converter.ConvertString(bs)\n// }\n"
  },
  {
    "path": "src/file/file.go",
    "content": "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 http://stackoverflow.com/users/1129149/swtdrgn\nfunc Unzip(src, dest string) error {\n\tr, err := zip.OpenReader(src)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer r.Close()\n\n\tfor _, f := range r.File {\n\t\tif !strings.Contains(f.Name, \"..\") {\n\t\t\trc, err := f.Open()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdefer rc.Close()\n\n\t\t\tfpath := filepath.Join(dest, f.Name)\n\t\t\tif f.FileInfo().IsDir() {\n\t\t\t\tos.MkdirAll(fpath, f.Mode())\n\t\t\t} else {\n\t\t\t\tvar fdir string\n\t\t\t\tif lastIndex := strings.LastIndex(fpath, string(os.PathSeparator)); lastIndex > -1 {\n\t\t\t\t\tfdir = fpath[:lastIndex]\n\t\t\t\t}\n\n\t\t\t\terr = os.MkdirAll(fdir, f.Mode())\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Fatal(err)\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tf, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tdefer f.Close()\n\n\t\t\t\t_, err = io.Copy(f, rc)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tlog.Printf(\"failed to extract file: %s (cannot validate)\\n\", f.Name)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc ReadLines(path string) ([]string, error) {\n\tfile, err := os.Open(path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer file.Close()\n\n\tvar lines []string\n\tscanner := bufio.NewScanner(file)\n\tfor scanner.Scan() {\n\t\tlines = append(lines, scanner.Text())\n\t}\n\treturn lines, scanner.Err()\n}\n\nfunc Exists(filename string) bool {\n\t_, err := os.Stat(filename)\n\treturn err == nil\n}\n"
  },
  {
    "path": "src/go.mod",
    "content": "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\tgithub.com/coreybutler/go-where v1.0.2\n\tgithub.com/dustin/go-humanize v1.0.1\n\tgithub.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4\n\tgithub.com/ncruces/zenity v0.10.14\n\tgithub.com/olekukonko/tablewriter v0.0.5\n\tgithub.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d\n\tgolang.org/x/sys v0.25.0\n)\n\nrequire (\n\tgithub.com/akavel/rsrc v0.10.2 // indirect\n\tgithub.com/dchest/jsmin v0.0.0-20220218165748-59f39799265f // indirect\n\tgithub.com/josephspurrier/goversioninfo v1.4.1 // indirect\n\tgithub.com/mattn/go-runewidth v0.0.9 // indirect\n\tgithub.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect\n\tgithub.com/randall77/makefat v0.0.0-20210315173500-7ddd0e42c844 // indirect\n\tgolang.org/x/image v0.20.0 // indirect\n)\n"
  },
  {
    "path": "src/go.sum",
    "content": "github.com/akavel/rsrc v0.10.2 h1:Zxm8V5eI1hW4gGaYsJQUhxpjkENuG91ki8B4zCrvEsw=\ngithub.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=\ngithub.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=\ngithub.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=\ngithub.com/coreybutler/go-fsutil v1.2.0 h1:kbm62NSofawglUppEOhpHC3NDf/J7ZpguBirBnsgUwU=\ngithub.com/coreybutler/go-fsutil v1.2.0/go.mod h1:B+6ufEkkRZgFwyR2sHEVG9dMzVBU3GbyGyYmCq7YkEk=\ngithub.com/coreybutler/go-where v1.0.2 h1:Omit67KeTtKpvSJjezVxnVD4qMtvlXDlItiKpVCdcl4=\ngithub.com/coreybutler/go-where v1.0.2/go.mod h1:IqV4saJiDXdNJURfTfVRywDHvY1IG5F+GXb2kmnmEe8=\ngithub.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/dchest/jsmin v0.0.0-20220218165748-59f39799265f h1:OGqDDftRTwrvUoL6pOG7rYTmWsTCvyEWFsMjg+HcOaA=\ngithub.com/dchest/jsmin v0.0.0-20220218165748-59f39799265f/go.mod h1:Dv9D0NUlAsaQcGQZa5kc5mqR9ua72SmA8VXi4cd+cBw=\ngithub.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=\ngithub.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=\ngithub.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4 h1:qZNfIGkIANxGv/OqtnntR4DfOY2+BgwR60cAcu/i3SE=\ngithub.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4/go.mod h1:kW3HQ4UdaAyrUCSSDR4xUzBKW6O2iA4uHhk7AtyYp10=\ngithub.com/josephspurrier/goversioninfo v1.4.1 h1:5LvrkP+n0tg91J9yTkoVnt/QgNnrI1t4uSsWjIonrqY=\ngithub.com/josephspurrier/goversioninfo v1.4.1/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY=\ngithub.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=\ngithub.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=\ngithub.com/ncruces/zenity v0.10.14 h1:OBFl7qfXcvsdo1NUEGxTlZvAakgWMqz9nG38TuiaGLI=\ngithub.com/ncruces/zenity v0.10.14/go.mod h1:ZBW7uVe/Di3IcRYH0Br8X59pi+O6EPnNIOU66YHpOO4=\ngithub.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=\ngithub.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=\ngithub.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=\ngithub.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/randall77/makefat v0.0.0-20210315173500-7ddd0e42c844 h1:GranzK4hv1/pqTIhMTXt2X8MmMOuH3hMeUR0o9SP5yc=\ngithub.com/randall77/makefat v0.0.0-20210315173500-7ddd0e42c844/go.mod h1:T1TLSfyWVBRXVGzWd0o9BI4kfoO9InEgfQe4NV3mLz8=\ngithub.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=\ngithub.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/zmb3/gogetdoc v0.0.0-20190228002656-b37376c5da6a/go.mod h1:ofmGw6LrMypycsiWcyug6516EXpIxSbZ+uI9ppGypfY=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw=\ngolang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=\ngolang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20181207195948-8634b1ecd393/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190825031127-d72b05d2b1b6/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "src/manifest.json",
    "content": "{\n  \"name\": \"nvm\",\n  \"version\": \"1.2.2\",\n  \"description\": \"Node.js version manager for Windows\",\n  \"license\": \"SEE LICENSE IN LICENSE\",\n  \"author\": \"coreybutler\",\n  \"build\": \"nvm.go\",\n  \"output\": \"../bin\",\n  \"update\": true,\n  \"upx\": false,\n  \"tiny\": false,\n  \"variables\": {\n    \"main.NvmVersion\": \"manifest.version\",\n    \"nvm/web.nvmversion\": \"manifest.version\"\n  },\n  \"minify\": true,\n  \"default_profiles\": [\n    \"dev\"\n  ],\n  \"profile\": {\n    \"release\": {\n      \"upx\": false,\n      \"variables\": {\n        \"main.debug\": \"false\",\n        \"main.mode\": \"prod\"\n      }\n    },\n    \"dev\": {\n      \"variables\": {\n        \"main.debug\": \"true\",\n        \"main.mode\": \"dev\"\n      }\n    }\n  }\n}"
  },
  {
    "path": "src/node/node.go",
    "content": "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\"\n\t\"strings\"\n\n\t// \"../semver\"\n\t\"github.com/blang/semver\"\n)\n\n/**\n * Returns version, architecture\n */\nfunc GetCurrentVersion() (string, string) {\n\tcmd := exec.Command(\"node\", \"-v\")\n\tstr, err := cmd.Output()\n\tif err == nil {\n\t\tv := strings.Trim(regexp.MustCompile(\"-.*$\").ReplaceAllString(regexp.MustCompile(\"v\").ReplaceAllString(strings.Trim(string(str), \" \\n\\r\"), \"\"), \"\"), \" \\n\\r\")\n\t\tcmd := exec.Command(\"node\", \"-p\", \"console.log(process.execPath)\")\n\t\tstr, _ := cmd.Output()\n\t\tfile := strings.Trim(regexp.MustCompile(\"undefined\").ReplaceAllString(string(str), \"\"), \" \\n\\r\")\n\t\tbit := arch.Bit(file)\n\t\tif bit == \"?\" {\n\t\t\tcmd := exec.Command(\"node\", \"-e\", \"console.log(process.arch)\")\n\t\t\tstr, err := cmd.Output()\n\t\t\tif err == nil {\n\t\t\t\tif string(str) == \"x64\" {\n\t\t\t\t\tbit = \"64\"\n\t\t\t\t} else if string(str) == \"arm64\" {\n\t\t\t\t\tbit = \"arm64\"\n\t\t\t\t} else {\n\t\t\t\t\tbit = \"32\"\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn v, \"Unknown\"\n\t\t\t}\n\t\t}\n\t\treturn v, bit\n\t}\n\treturn \"Unknown\", \"\"\n}\n\nfunc IsVersionInstalled(root string, version string, cpu string) bool {\n\te32 := file.Exists(root + \"\\\\v\" + version + \"\\\\node32.exe\")\n\te64 := file.Exists(root + \"\\\\v\" + version + \"\\\\node64.exe\")\n\tused := file.Exists(root + \"\\\\v\" + version + \"\\\\node.exe\")\n\tif cpu == \"all\" {\n\t\treturn ((e32 || e64) && used) || e32 && e64\n\t}\n\tif file.Exists(root + \"\\\\v\" + version + \"\\\\node\" + cpu + \".exe\") {\n\t\treturn true\n\t}\n\tif ((e32 || e64) && used) || (e32 && e64) {\n\t\treturn true\n\t}\n\tif !e32 && !e64 && used && arch.Validate(cpu) == arch.Bit(root+\"\\\\v\"+version+\"\\\\node.exe\") {\n\t\treturn true\n\t}\n\tif cpu == \"32\" {\n\t\treturn e32\n\t}\n\tif cpu == \"64\" {\n\t\treturn e64\n\t}\n\treturn false\n}\n\nfunc IsVersionAvailable(v string) bool {\n\t// Check the service to make sure the version is available\n\tavail, _, _, _, _, _ := GetAvailable()\n\n\tfor _, b := range avail {\n\t\tif b == v {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc reverseStringArray(str []string) []string {\n\tfor i := 0; i < len(str)/2; i++ {\n\t\tj := len(str) - i - 1\n\t\tstr[i], str[j] = str[j], str[i]\n\t}\n\n\treturn str\n}\n\nfunc GetInstalled(root string) []string {\n\tlist := make([]semver.Version, 0)\n\tfiles, _ := ioutil.ReadDir(root)\n\n\tfor i := len(files) - 1; i >= 0; i-- {\n\t\tif files[i].IsDir() || (files[i].Mode()&os.ModeSymlink == os.ModeSymlink) {\n\t\t\tisnode, _ := regexp.MatchString(\"v\", files[i].Name())\n\n\t\t\tif isnode {\n\t\t\t\tcurrentVersionString := strings.Replace(files[i].Name(), \"v\", \"\", 1)\n\t\t\t\tcurrentVersion, _ := semver.Make(currentVersionString)\n\n\t\t\t\tlist = append(list, currentVersion)\n\t\t\t}\n\t\t}\n\t}\n\n\tsemver.Sort(list)\n\n\tloggableList := make([]string, 0)\n\n\tfor _, version := range list {\n\t\tloggableList = append(loggableList, \"v\"+version.String())\n\t}\n\n\tloggableList = reverseStringArray(loggableList)\n\n\treturn loggableList\n}\n\n// Sorting\ntype BySemanticVersion []string\n\nfunc (s BySemanticVersion) Len() int {\n\treturn len(s)\n}\n\nfunc (s BySemanticVersion) Swap(i, j int) {\n\ts[i], s[j] = s[j], s[i]\n}\n\nfunc (s BySemanticVersion) Less(i, j int) bool {\n\tv1, _ := semver.Make(s[i])\n\tv2, _ := semver.Make(s[j])\n\treturn v1.GTE(v2)\n}\n\n// Identifies a version as \"LTS\"\nfunc isLTS(element map[string]interface{}) bool {\n\tswitch datatype := element[\"lts\"].(type) {\n\tcase bool:\n\t\treturn datatype\n\tcase string:\n\t\treturn true\n\t}\n\treturn false\n}\n\n// Identifies a version as \"current\"\nfunc isCurrent(element map[string]interface{}) bool {\n\tif isLTS(element) {\n\t\treturn false\n\t}\n\n\tversion, _ := semver.Make(element[\"version\"].(string)[1:])\n\tbenchmark, _ := semver.Make(\"1.0.0\")\n\n\tif version.LT(benchmark) {\n\t\treturn false\n\t}\n\n\treturn true\n\t// return version.Major%2 == 1\n}\n\n// Identifies a stable old version.\nfunc isStable(element map[string]interface{}) bool {\n\tif isCurrent(element) {\n\t\treturn false\n\t}\n\n\tversion, _ := semver.Make(element[\"version\"].(string)[1:])\n\n\tif version.Major != 0 {\n\t\treturn false\n\t}\n\n\treturn version.Minor%2 == 0\n}\n\n// Identifies an unstable old version.\nfunc isUnstable(element map[string]interface{}) bool {\n\tif isStable(element) {\n\t\treturn false\n\t}\n\n\tversion, _ := semver.Make(element[\"version\"].(string)[1:])\n\n\tif version.Major != 0 {\n\t\treturn false\n\t}\n\n\treturn version.Minor%2 != 0\n}\n\n// Retrieve the remotely available versions\nfunc GetAvailable() ([]string, []string, []string, []string, []string, map[string]string) {\n\tall := make([]string, 0)\n\tlts := make([]string, 0)\n\tcurrent := make([]string, 0)\n\tstable := make([]string, 0)\n\tunstable := make([]string, 0)\n\tnpm := make(map[string]string)\n\turl := web.GetFullNodeUrl(\"index.json\")\n\n\t// Check the service to make sure the version is available\n\ttext, err := web.GetRemoteTextFile(url)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\tos.Exit(1)\n\t}\n\tif len(text) == 0 {\n\t\tfmt.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.\")\n\t\tos.Exit(0)\n\t}\n\n\t// Parse\n\tvar data = make([]map[string]interface{}, 0)\n\terr = json.Unmarshal([]byte(text), &data)\n\tif err != nil {\n\t\tfmt.Printf(\"Error retrieving versions from \\\"%s\\\": %v\", url, err.Error())\n\t\tos.Exit(1)\n\t}\n\n\tfor _, element := range data {\n\t\tvar version = element[\"version\"].(string)[1:]\n\t\tall = append(all, version)\n\n\t\tif val, ok := element[\"npm\"].(string); ok {\n\t\t\tnpm[version] = val\n\t\t}\n\n\t\tif isLTS(element) {\n\t\t\tlts = append(lts, version)\n\t\t} else if isCurrent(element) {\n\t\t\tcurrent = append(current, version)\n\t\t} else if isStable(element) {\n\t\t\tstable = append(stable, version)\n\t\t} else if isUnstable(element) {\n\t\t\tunstable = append(unstable, version)\n\t\t}\n\t}\n\n\treturn all, lts, current, stable, unstable, npm\n}\n"
  },
  {
    "path": "src/nvm.go",
    "content": "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\t\"os/user\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"syscall\"\n\t\"time\"\n\t\"unsafe\"\n\n\t\"nvm/arch\"\n\t\"nvm/author\"\n\t\"nvm/encoding\"\n\t\"nvm/file\"\n\t\"nvm/node\"\n\t\"nvm/upgrade\"\n\t\"nvm/utility\"\n\t\"nvm/web\"\n\n\t\"github.com/blang/semver\"\n\n\t// \"github.com/fatih/color\"\n\n\t\"github.com/coreybutler/go-where\"\n\t\"github.com/ncruces/zenity\"\n\t\"github.com/olekukonko/tablewriter\"\n\t\"golang.org/x/sys/windows\"\n\t\"golang.org/x/sys/windows/registry\"\n)\n\n// Replaced at build time\nvar NvmVersion = \"\"\n\ntype Environment struct {\n\tsettings        string\n\troot            string\n\tsymlink         string\n\tarch            string\n\tnode_mirror     string\n\tnpm_mirror      string\n\tproxy           string\n\toriginalpath    string\n\toriginalversion string\n\tverifyssl       bool\n}\n\nvar home = filepath.Clean(os.Getenv(\"NVM_HOME\") + \"\\\\settings.txt\")\nvar symlink = filepath.Clean(os.Getenv(\"NVM_SYMLINK\"))\n\nvar env = &Environment{\n\tsettings:        home,\n\troot:            \"\",\n\tsymlink:         symlink,\n\tarch:            strings.ToLower(os.Getenv(\"PROCESSOR_ARCHITECTURE\")),\n\tnode_mirror:     \"\",\n\tnpm_mirror:      \"\",\n\tproxy:           \"none\",\n\toriginalpath:    \"\",\n\toriginalversion: \"\",\n\tverifyssl:       true,\n}\n\nfunc writeToErrorLog(i interface{}, abort ...bool) {\n\texe, _ := os.Executable()\n\tfile, err := os.OpenFile(filepath.Join(filepath.Dir(exe), \"error.log\"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, os.ModePerm)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer file.Close()\n\n\tmsg := fmt.Sprintf(\"%v\\n\", i)\n\tif _, ferr := file.WriteString(msg); ferr != nil {\n\t\tpanic(ferr)\n\t}\n\n\tif len(abort) > 0 && abort[0] {\n\t\tfmt.Println(msg)\n\t\tos.Exit(1)\n\t}\n}\n\ntype Notification struct {\n\tAppID    string   `json:\"app_id\"`\n\tTitle    string   `json:\"title\"`\n\tMessage  string   `json:\"message\"`\n\tIcon     string   `json:\"icon\"`\n\tActions  []Action `json:\"actions\"`\n\tDuration string   `json:\"duration\"`\n\tLink     string   `json:\"link\"`\n}\n\ntype Action struct {\n\tType  string `json:\"type\"`\n\tLabel string `json:\"label\"`\n\tURI   string `json:\"uri\"`\n}\n\nfunc notify(data Notification) {\n\tdata.AppID = \"NVM for Windows\"\n\tcontent, _ := json.Marshal(data)\n\tgo author.Bridge(\"notify\", string(content))\n}\n\nfunc init() {\n\tif len(os.Args) > 1 {\n\t\tif strings.HasPrefix(os.Args[1], \"nvm://\") {\n\t\t\t// Decode the URL\n\t\t\turi, _ := url.Parse(strings.ReplaceAll(os.Args[1], \"%26\", \"&\"))\n\t\t\taction := strings.TrimSpace(uri.Query().Get(\"action\"))\n\n\t\t\tswitch strings.ToLower(action) {\n\t\t\tcase \"install\":\n\t\t\t\tversion := strings.Replace(uri.Query().Get(\"version\"), \"v\", \"\", 1)\n\n\t\t\t\tos.Args[1] = \"install\"\n\t\t\t\tif len(os.Args) < 3 {\n\t\t\t\t\tos.Args = append(os.Args, \"\")\n\t\t\t\t}\n\t\t\t\tos.Args[2] = version\n\t\t\t\tif len(os.Args) < 4 {\n\t\t\t\t\tos.Args = append(os.Args, \"\")\n\t\t\t\t}\n\t\t\t\tos.Args[3] = \"--show-progress-ui\"\n\t\t\t\tos.Args = append(os.Args, \"--insecure\")\n\n\t\t\tcase \"use\":\n\t\t\t\tversion := strings.Replace(uri.Query().Get(\"version\"), \"v\", \"\", 1)\n\n\t\t\t\tos.Args[1] = \"use\"\n\t\t\t\tif len(os.Args) < 3 {\n\t\t\t\t\tos.Args = append(os.Args, \"\")\n\t\t\t\t}\n\t\t\t\tos.Args[2] = version\n\t\t\t\tif len(os.Args) < 4 {\n\t\t\t\t\tos.Args = append(os.Args, \"\")\n\t\t\t\t}\n\t\t\t\tos.Args[3] = \"--notify\"\n\n\t\t\tcase \"upgrade\":\n\t\t\t\tos.Args[1] = \"upgrade\"\n\t\t\t\tif len(os.Args) < 3 {\n\t\t\t\t\tos.Args = append(os.Args, \"\")\n\t\t\t\t}\n\t\t\t\tos.Args[2] = \"--show-progress-ui\"\n\n\t\t\tcase \"upgrade_notify\":\n\t\t\t\tnotify(Notification{\n\t\t\t\t\tTitle:   \"Upgrade Complete\",\n\t\t\t\t\tMessage: fmt.Sprintf(\"Now running v%v.\", NvmVersion),\n\t\t\t\t\tIcon:    \"success\",\n\t\t\t\t\tActions: []Action{\n\t\t\t\t\t\t{Type: \"protocol\", Label: \"Release Notes\", URI: fmt.Sprintf(\"https://github.com/coreybutler/nvm-windows/releases/tag/%v\", NvmVersion)},\n\t\t\t\t\t},\n\t\t\t\t})\n\n\t\t\t\ttime.Sleep(300 * time.Millisecond)\n\n\t\t\t\tos.Exit(0)\n\t\t\tdefault:\n\t\t\t\twriteToErrorLog(fmt.Sprintf(\"%s command not recognized\", action), true)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Turn on debugging output\n\tfor _, arg := range os.Args[1:] {\n\t\tif strings.ToLower(strings.ReplaceAll(arg, \"-\", \"\")) == \"verbose\" {\n\t\t\tutility.EnableDebugLogs()\n\t\t\tbreak\n\t\t}\n\t}\n}\n\nfunc main() {\n\tutility.DebugLogf(\"command: %v\", strings.Join(os.Args, \" \"))\n\targs := os.Args\n\tdetail := \"\"\n\tprocarch := arch.Validate(env.arch)\n\n\t// Capture any additional arguments\n\tif len(args) > 2 {\n\t\tdetail = args[2]\n\t}\n\tif len(args) > 3 {\n\t\tif args[3] == \"32\" || args[3] == \"arm64\" || args[3] == \"64\" {\n\t\t\tprocarch = args[3]\n\t\t}\n\t}\n\tif len(args) < 2 {\n\t\thelp()\n\t\treturn\n\t}\n\n\tutility.DebugLogf(\"arch: %v\", procarch)\n\n\tif args[1] != \"version\" && args[1] != \"--version\" && args[1] != \"v\" && args[1] != \"-v\" && args[1] != \"--v\" {\n\t\tsetup()\n\t}\n\n\t// Run the appropriate method\n\tswitch args[1] {\n\tcase \"i\":\n\t\tfallthrough\n\tcase \"install\":\n\t\tinstall(detail, procarch)\n\tcase \"rm\":\n\t\tfallthrough\n\tcase \"uninstall\":\n\t\tuninstall(detail)\n\tcase \"reinstall\":\n\t\treinstall(detail, procarch)\n\tcase \"u\":\n\t\tfallthrough\n\tcase \"use\":\n\t\tuse(detail, procarch)\n\tcase \"ls\":\n\t\tfallthrough\n\tcase \"list\":\n\t\tlist(detail)\n\tcase \"on\":\n\t\tenable()\n\tcase \"off\":\n\t\tdisable()\n\tcase \"root\":\n\t\tif len(args) == 3 {\n\t\t\tupdateRootDir(args[2])\n\t\t} else {\n\t\t\tfmt.Println(\"\\nCurrent Root: \" + env.root)\n\t\t}\n\tcase \"v\":\n\t\tfallthrough\n\tcase \"--version\":\n\t\tfallthrough\n\tcase \"-version\":\n\t\tfallthrough\n\tcase \"--v\":\n\t\tfallthrough\n\tcase \"-v\":\n\t\tfallthrough\n\tcase \"version\":\n\t\tfmt.Println(NvmVersion)\n\tcase \"arch\":\n\t\tif strings.Trim(detail, \" \\r\\n\") != \"\" {\n\t\t\tdetail = strings.Trim(detail, \" \\r\\n\")\n\t\t\tif detail != \"32\" && detail != \"64\" && detail != \"arm64\" {\n\t\t\t\tfmt.Println(\"\\\"\" + detail + \"\\\" is an invalid architecture. Use 32, 64, or arm64.\")\n\t\t\t\treturn\n\t\t\t}\n\t\t\tenv.arch = detail\n\t\t\tsaveSettings()\n\t\t\tfmt.Println(\"Default architecture set to \" + detail + \"-bit.\")\n\t\t\treturn\n\t\t}\n\t\t_, a := node.GetCurrentVersion()\n\t\tfmt.Println(\"System Default: \" + env.arch + \"-bit.\")\n\t\tfmt.Println(\"Currently Configured: \" + a + \"-bit.\")\n\tcase \"proxy\":\n\t\tif detail == \"\" {\n\t\t\tfmt.Println(\"Current proxy: \" + env.proxy)\n\t\t} else {\n\t\t\tenv.proxy = detail\n\t\t\tsaveSettings()\n\t\t}\n\tcase \"current\":\n\t\tinuse, _ := node.GetCurrentVersion()\n\t\tv, _ := semver.Make(inuse)\n\t\terr := v.Validate()\n\n\t\tif err != nil {\n\t\t\tfmt.Println(inuse)\n\t\t} else if inuse == \"Unknown\" {\n\t\t\tfmt.Println(\"No current version. Run 'nvm use x.x.x' to set a version.\")\n\t\t} else {\n\t\t\tfmt.Println(\"v\" + inuse)\n\t\t}\n\n\t//case \"update\": update()\n\tcase \"node_mirror\":\n\t\tsetNodeMirror(detail)\n\tcase \"npm_mirror\":\n\t\tsetNpmMirror(detail)\n\tcase \"debug\":\n\t\tcheckLocalEnvironment()\n\tcase \"subscribe\":\n\t\tfallthrough\n\tcase \"unsubscribe\":\n\t\tauthor.Bridge(args[1:]...)\n\tcase \"author\":\n\t\tauthor.Bridge(args[2:]...)\n\tcase \"upgrade\":\n\t\tupgrade.Run(NvmVersion)\n\tdefault:\n\t\tfmt.Printf(`\"%s\" is not a valid command.`+\"\\n\", args[1])\n\t\thelp()\n\t}\n}\n\n// ===============================================================\n// BEGIN | CLI functions\n// ===============================================================\nfunc setNodeMirror(uri string) {\n\tenv.node_mirror = uri\n\tsaveSettings()\n}\n\nfunc setNpmMirror(uri string) {\n\tenv.npm_mirror = uri\n\tsaveSettings()\n}\n\nfunc getVersion(version string, cpuarch string, localInstallsOnly ...bool) (string, string, error) {\n\trequestedVersion := version\n\tcpuarch = strings.ToLower(cpuarch)\n\n\tif cpuarch != \"\" {\n\t\tif cpuarch != \"32\" && cpuarch != \"64\" && cpuarch != \"arm64\" && cpuarch != \"all\" {\n\t\t\treturn version, cpuarch, errors.New(\"\\\"\" + cpuarch + \"\\\" is not a valid CPU architecture. Must be 32, 64, or arm64.\")\n\t\t}\n\t} else {\n\t\tcpuarch = env.arch\n\t}\n\n\tif cpuarch != \"all\" {\n\t\tcpuarch = arch.Validate(cpuarch)\n\t}\n\n\tif version == \"\" {\n\t\treturn \"\", cpuarch, errors.New(\"A version argument is required but missing.\")\n\t}\n\n\t// If user specifies \"latest\" version, find out what version is\n\tif version == \"latest\" || version == \"node\" {\n\t\tversion = getLatest()\n\t\tfmt.Println(version)\n\t}\n\n\tif version == \"lts\" {\n\t\tversion = getLTS()\n\t}\n\n\tif version == \"newest\" {\n\t\tinstalled := node.GetInstalled(env.root)\n\t\tif len(installed) == 0 {\n\t\t\treturn version, \"\", errors.New(\"No versions of node.js found. Try installing the latest by typing nvm install latest.\")\n\t\t}\n\n\t\tversion = installed[0]\n\t}\n\n\tif version == \"32\" || version == \"64\" || version == \"arm64\" {\n\t\tcpuarch = version\n\t\tv, _ := node.GetCurrentVersion()\n\t\tversion = v\n\t}\n\n\tversion = versionNumberFrom(version)\n\tv, err := semver.Make(version)\n\tif err == nil {\n\t\terr = v.Validate()\n\t}\n\n\tif err == nil {\n\t\t// if the user specifies only the major/minor version, identify the latest\n\t\t// version applicable to what was provided.\n\t\tsv := strings.Split(version, \".\")\n\t\tif len(sv) < 3 {\n\t\t\tversion = findLatestSubVersion(version)\n\t\t} else {\n\t\t\tversion = cleanVersion(version)\n\t\t}\n\n\t\tversion = versionNumberFrom(version)\n\t} else if strings.Contains(err.Error(), \"No Major.Minor.Patch\") {\n\t\tlatestLocalInstall := false\n\t\tif len(localInstallsOnly) > 0 {\n\t\t\tlatestLocalInstall = localInstallsOnly[0]\n\t\t}\n\t\tversion = findLatestSubVersion(version, latestLocalInstall)\n\t\tif len(version) == 0 {\n\t\t\terr = errors.New(\"Unrecognized version: \\\"\" + requestedVersion + \"\\\"\")\n\t\t}\n\t}\n\n\treturn version, cpuarch, err\n}\n\ntype Status struct {\n\tText string\n\tErr  error\n\tDone bool\n\tHelp bool\n}\n\nfunc rollback(version string) error {\n\tp := filepath.Join(env.root, \"v\"+version)\n\n\t_, err := os.Lstat(p)\n\tif err != nil {\n\t\tif !os.IsNotExist(err) {\n\t\t\twriteToErrorLog(err)\n\t\t\treturn fmt.Errorf(\"Error rolling back node v%s installation: %v.\", version, err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc install(version string, cpuarch string) {\n\trequestedVersion := version\n\targs := os.Args\n\tlastarg := args[len(args)-1]\n\n\tif lastarg == \"--insecure\" {\n\t\tenv.verifyssl = false\n\t}\n\n\tif strings.HasPrefix(version, \"--\") {\n\t\tfmt.Println(\"\\\"--\\\" prefixes are unnecessary in NVM for Windows!\")\n\t\tversion = strings.ReplaceAll(version, \"-\", \"\")\n\t\tfmt.Printf(\"attempting to install \\\"%v\\\" instead...\\n\\n\", version)\n\t\ttime.Sleep(2 * time.Second)\n\t}\n\n\tvar exitCode = 0\n\tvar status = make(chan Status)\n\tvar cancel = make(chan bool)\n\tvar show_progress bool = false\n\tvar dlg zenity.ProgressDialog\n\n\twg := &sync.WaitGroup{}\n\twg.Add(1)\n\n\tgo func() {\n\t\tdefer func() {\n\t\t\t// Reset the SSL verification\n\t\t\tenv.verifyssl = true\n\n\t\t\t// sleep for 1 second to give users a chance to see the completion notice before exiting\n\t\t\tif show_progress {\n\t\t\t\ttime.Sleep(1 * time.Second)\n\t\t\t\tfmt.Println(\"exiting installer dialog\")\n\t\t\t}\n\n\t\t\twg.Done()\n\t\t}()\n\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase s := <-status:\n\t\t\t\tif s.Err != nil {\n\t\t\t\t\texitCode = 1\n\t\t\t\t\tif show_progress && dlg != nil {\n\t\t\t\t\t\t// Close progress dialog and send error notificaion\n\t\t\t\t\t\tnotify(Notification{\n\t\t\t\t\t\t\tTitle:   \"Node.js Installation Error\",\n\t\t\t\t\t\t\tMessage: s.Err.Error(),\n\t\t\t\t\t\t\tIcon:    \"error\",\n\t\t\t\t\t\t})\n\n\t\t\t\t\t\tdlg.Text(fmt.Sprintf(\"error: %v\", s.Err))\n\t\t\t\t\t\tdlg.Close()\n\n\t\t\t\t\t\t// Cleanup\n\t\t\t\t\t\terr := rollback(version)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tnotify(Notification{\n\t\t\t\t\t\t\t\tTitle:   \"Rollback Error\",\n\t\t\t\t\t\t\t\tMessage: err.Error(),\n\t\t\t\t\t\t\t\tIcon:    \"error\",\n\t\t\t\t\t\t\t\tActions: []Action{\n\t\t\t\t\t\t\t\t\t{Type: \"protocol\", Label: \"Open Explorer\", URI: fmt.Sprintf(\"file://%s\", filepath.ToSlash(filepath.Join(env.root)))},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfmt.Printf(\"error installing %s: %v\\n\", version, s.Err)\n\t\t\t\t\t\tif s.Help {\n\t\t\t\t\t\t\tfmt.Println(\" \")\n\t\t\t\t\t\t\thelp()\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif s.Done {\n\t\t\t\t\tif show_progress && dlg != nil {\n\t\t\t\t\t\tnotify(Notification{\n\t\t\t\t\t\t\tTitle:   fmt.Sprintf(\"Node.js v%s\", version),\n\t\t\t\t\t\t\tMessage: \"Installation complete.\",\n\t\t\t\t\t\t\tIcon:    \"node\",\n\t\t\t\t\t\t\tActions: []Action{\n\t\t\t\t\t\t\t\t{Type: \"protocol\", Label: \"Use\", URI: fmt.Sprintf(\"nvm://launch?action=use%%26version=%s\", version)},\n\t\t\t\t\t\t\t\t{Type: \"protocol\", Label: \"Changelog\", URI: fmt.Sprintf(\"https://github.com/nodejs/node/releases/tag/v%s\", version)},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t})\n\n\t\t\t\t\t\tdlg.Text(\"Installation complete.\")\n\t\t\t\t\t\ttime.Sleep(1 * time.Second)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfmt.Printf(\"Installation complete.\\nIf you want to use this version, type:\\n\\nnvm use %s\\n\", version)\n\t\t\t\t\t}\n\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif show_progress && dlg != nil {\n\t\t\t\t\tdlg.Text(s.Text)\n\t\t\t\t} else {\n\t\t\t\t\tfmt.Println(s.Text)\n\t\t\t\t}\n\t\t\tcase <-cancel:\n\t\t\t\tfmt.Printf(\"Node.js %s installation canceled by user\\n\", version)\n\n\t\t\t\tif show_progress {\n\t\t\t\t\tnotify(Notification{\n\t\t\t\t\t\tTitle:   fmt.Sprintf(\"Node.js v%s\", version),\n\t\t\t\t\t\tMessage: \"Installation canceled by user\",\n\t\t\t\t\t\tIcon:    \"error\",\n\t\t\t\t\t\tActions: []Action{\n\t\t\t\t\t\t\t{Type: \"protocol\", Label: \"Restart Installation\", URI: fmt.Sprintf(\"nvm://launch?action=install%%26version=%s%%26use=false%%26=show=true\", version)},\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t}\n\n\t\t\t\terr := rollback(version)\n\t\t\t\tif err != nil {\n\t\t\t\t\tif show_progress {\n\t\t\t\t\t\tnotify(Notification{\n\t\t\t\t\t\t\tTitle:   \"Rollback Error\",\n\t\t\t\t\t\t\tMessage: err.Error(),\n\t\t\t\t\t\t\tIcon:    \"error\",\n\t\t\t\t\t\t\tActions: []Action{\n\t\t\t\t\t\t\t\t{Type: \"protocol\", Label: \"Open Explorer\", URI: fmt.Sprintf(\"file://%s\", filepath.ToSlash(filepath.Join(env.root)))},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t})\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfmt.Printf(\"rollback error: %v\\n\", err)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tfmt.Println(\"Rollback complete.\")\n\t\t\t\t}\n\n\t\t\t\tif cpuarch == \"arm64\" && !web.IsNodeArm64bitAvailable(version) {\n\t\t\t\t\tstatus <- Status{Err: fmt.Errorf(\"Node.js v%s is only available in 32-bit and 64-bit.\", version)}\n\t\t\t\t}\n\n\t\t\t\tif !node.IsVersionInstalled(env.root, version, cpuarch) {\n\t\t\t\t\tif !node.IsVersionAvailable(version) {\n\t\t\t\t\t\turl := web.GetFullNodeUrl(\"index.json\")\n\t\t\t\t\t\tstatus <- Status{Err: fmt.Errorf(\"Version %s is not available.\\n\\nThe complete list of available versions can be found at %s\", version, url)}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\t// Wait for the prior subroutine to initialize before starting the next dependent thread\n\ttime.Sleep(300 * time.Millisecond)\n\n\tgo func() {\n\t\tv, a, err := getVersion(version, cpuarch)\n\t\tversion = v\n\t\tcpuarch = a\n\n\t\t// Setup signal handling first\n\t\tsignalChan := make(chan os.Signal, 1)\n\t\tsignal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)\n\t\tdefer signal.Stop(signalChan)\n\n\t\t// Add signal handler\n\t\tgo func() {\n\t\t\t<-signalChan\n\t\t\tcancel <- true\n\t\t}()\n\n\t\t// Determine whether to show the progress dialog\n\t\tfor _, arg := range os.Args {\n\t\t\tif arg == \"--show-progress-ui\" {\n\t\t\t\tshow_progress = true\n\n\t\t\t\texe, _ := os.Executable()\n\t\t\t\twinIco := filepath.Join(filepath.Dir(exe), \"nvm.ico\")\n\t\t\t\tico := filepath.Join(filepath.Dir(exe), \"nodejs.ico\")\n\n\t\t\t\tvar perr error\n\t\t\t\tdlg, perr = zenity.Progress(\n\t\t\t\t\tzenity.Title(fmt.Sprintf(\"Installing Node.js v%s\", version)),\n\t\t\t\t\tzenity.Icon(ico),\n\t\t\t\t\tzenity.WindowIcon(winIco),\n\t\t\t\t\tzenity.AutoClose(),\n\t\t\t\t\tzenity.NoCancel(),\n\t\t\t\t\tzenity.Pulsate())\n\t\t\t\tif perr != nil {\n\t\t\t\t\tfmt.Println(\"Failed to create progress dialog\")\n\t\t\t\t}\n\t\t\t\tgo func() {\n\t\t\t\t\tfor {\n\t\t\t\t\t\tselect {\n\t\t\t\t\t\tcase <-dlg.Done():\n\t\t\t\t\t\t\tif err := dlg.Complete(); err == zenity.ErrCanceled {\n\t\t\t\t\t\t\t\tcancel <- true\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t\tstatus <- Status{Text: \"Validating version...\"}\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif err != nil {\n\t\t\tif strings.Contains(err.Error(), \"No Major.Minor.Patch\") {\n\t\t\t\tsv, sverr := semver.Make(version)\n\t\t\t\tif sverr == nil {\n\t\t\t\t\tsverr = sv.Validate()\n\t\t\t\t}\n\t\t\t\tif sverr != nil {\n\t\t\t\t\tversion = findLatestSubVersion(version)\n\t\t\t\t\tif len(version) == 0 {\n\t\t\t\t\t\tsverr = errors.New(\"Unrecognized version: \\\"\" + requestedVersion + \"\\\"\")\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\terr = sverr\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tstatus <- Status{Err: err, Help: true}\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tif err != nil {\n\t\t\tstatus <- Status{Err: fmt.Errorf(`\"%s\" is not a valid version.`+\"\\n\"+`Please use a valid semantic version number, \"lts\", or \"latest\".`, requestedVersion)}\n\t\t\treturn\n\t\t}\n\n\t\tif checkVersionExceedsLatest(version) {\n\t\t\tstatus <- Status{Err: fmt.Errorf(\"Node.js v%s is not yet released or is not available for download yet.\", version)}\n\t\t\treturn\n\t\t}\n\n\t\tif cpuarch == \"64\" && !web.IsNode64bitAvailable(version) {\n\t\t\tstatus <- Status{Err: fmt.Errorf(\"Node.js v%s is only available in 32-bit.\", version)}\n\t\t\treturn\n\t\t}\n\n\t\t// Check to see if the version is already installed\n\t\tif !node.IsVersionInstalled(env.root, version, cpuarch) {\n\t\t\tif !node.IsVersionAvailable(version) {\n\t\t\t\turl := web.GetFullNodeUrl(\"index.json\")\n\t\t\t\tstatus <- Status{Err: fmt.Errorf(\"Version %s is not available.\\n\\nThe complete list of available versions can be found at %s\", version, url)}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Make the output directories\n\t\t\troot, err := os.MkdirTemp(\"\", \"nvm-install-*\")\n\t\t\tif err != nil {\n\t\t\t\tstatus <- Status{Err: err}\n\t\t\t}\n\t\t\tdefer os.RemoveAll(root)\n\t\t\tos.MkdirAll(filepath.Join(root, \"v\"+version, \"node_modules\"), os.ModeDir)\n\t\t\t// os.MkdirAll(filepath.Join(env.root, \"v\"+version, \"node_modules\"), os.ModeDir)\n\n\t\t\t// Warn the user if they're attempting to install without verifying the remote SSL cert\n\t\t\tif !env.verifyssl {\n\t\t\t\tfmt.Println(\"\\nWARNING: The remote SSL certificate will not be validated during the download process.\\n\")\n\t\t\t}\n\n\t\t\t// Download node\n\t\t\tif show_progress {\n\t\t\t\tstatus <- Status{Text: \"Downloading & extracting...\"}\n\t\t\t}\n\t\t\tappend32 := node.IsVersionInstalled(env.root, version, \"64\")\n\t\t\tappend64 := node.IsVersionInstalled(env.root, version, \"32\")\n\t\t\tif (cpuarch == \"32\" || cpuarch == \"all\") && !node.IsVersionInstalled(root, version, \"32\") {\n\t\t\t\tsuccess := web.GetNodeJS(root, version, \"32\", append32)\n\t\t\t\tif !success {\n\t\t\t\t\tstatus <- Status{Err: fmt.Errorf(\"failed to download v%v 32-bit executable\", version)}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (cpuarch == \"64\" || cpuarch == \"all\") && !node.IsVersionInstalled(root, version, \"64\") {\n\t\t\t\tsuccess := web.GetNodeJS(root, version, \"64\", append64)\n\t\t\t\tif !success {\n\t\t\t\t\tstatus <- Status{Err: fmt.Errorf(\"failed to download v%v 64-bit executable\", version)}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (cpuarch == \"arm64\" || cpuarch == \"all\") && !node.IsVersionInstalled(root, version, \"arm64\") {\n\t\t\t\tsuccess := web.GetNodeJS(root, version, \"arm64\", append64)\n\t\t\t\tif !success {\n\t\t\t\t\tstatus <- Status{Err: fmt.Errorf(\"failed to download v%v arm 64-bit executable\", version)}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif file.Exists(filepath.Join(root, \"v\"+version, \"node_modules\", \"npm\")) {\n\t\t\t\tutility.DebugLogf(\"move %v to %v\", filepath.Join(root, \"v\"+version), filepath.Join(env.root, \"v\"+version))\n\t\t\t\tif rnerr := utility.Rename(filepath.Join(root, \"v\"+version), filepath.Join(env.root, \"v\"+version)); rnerr != nil {\n\t\t\t\t\tstatus <- Status{Err: err}\n\t\t\t\t}\n\t\t\t\tutility.DebugFn(func() {\n\t\t\t\t\tutility.DebugLogf(\"env root: %v\", env.root)\n\t\t\t\t\tcmd := exec.Command(\"cmd\", \"/C\", \"dir\", filepath.Join(env.root, \"v\"+version))\n\t\t\t\t\tout, err := cmd.CombinedOutput()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tutility.DebugLog(err.Error())\n\t\t\t\t\t} else {\n\t\t\t\t\t\tutility.DebugLog(string(out))\n\t\t\t\t\t}\n\t\t\t\t})\n\n\t\t\t\tif show_progress {\n\t\t\t\t\tstatus <- Status{Text: \"Configuring npm...\"}\n\t\t\t\t\ttime.Sleep(1 * time.Second)\n\t\t\t\t\tstatus <- Status{Done: true}\n\t\t\t\t} else {\n\t\t\t\t\tnpmv := getNpmVersion(version)\n\t\t\t\t\tstatus <- 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}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If successful, add npm\n\t\t\tstatus <- Status{Text: \"Downloading npm...\"}\n\t\t\tnpmv := getNpmVersion(version)\n\t\t\tsuccess := web.GetNpm(root, getNpmVersion(version))\n\t\t\tif success {\n\t\t\t\tstatus <- Status{Text: fmt.Sprintf(\"Installing npm v%s...\", npmv)}\n\n\t\t\t\t// new temp directory under the nvm root\n\t\t\t\ttempDir, err := os.MkdirTemp(\"\", \"nvm-npm-*\")\n\t\t\t\tif err != nil {\n\t\t\t\t\tstatus <- Status{Err: err}\n\t\t\t\t}\n\t\t\t\tdefer os.RemoveAll(tempDir)\n\n\t\t\t\t// Extract npm to the temp directory\n\t\t\t\terr = file.Unzip(filepath.Join(tempDir, \"npm-v\"+npmv+\".zip\"), filepath.Join(tempDir, \"nvm-npm\"))\n\t\t\t\tif err != nil {\n\t\t\t\t\tstatus <- Status{Err: err}\n\t\t\t\t}\n\n\t\t\t\t// Copy the npm and npm.cmd files to the installation directory\n\t\t\t\ttempNpmBin := filepath.Join(tempDir, \"nvm-npm\", \"cli-\"+npmv, \"bin\")\n\n\t\t\t\t// Support npm < 6.2.0\n\t\t\t\tif file.Exists(tempNpmBin) == false {\n\t\t\t\t\ttempNpmBin = filepath.Join(tempDir, \"nvm-npm\", \"npm-\"+npmv, \"bin\")\n\t\t\t\t}\n\n\t\t\t\tif file.Exists(tempNpmBin) == false {\n\t\t\t\t\tstatus <- Status{Err: fmt.Errorf(\"Failed to extract npm. Could not find %s\", tempNpmBin), Done: true}\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t// Standard npm support\n\t\t\t\tutility.Rename(filepath.Join(tempNpmBin, \"npm\"), filepath.Join(root, \"v\"+version, \"npm\"))\n\t\t\t\tutility.Rename(filepath.Join(tempNpmBin, \"npm.cmd\"), filepath.Join(root, \"v\"+version, \"npm.cmd\"))\n\n\t\t\t\t// npx support\n\t\t\t\tif _, err := os.Stat(filepath.Join(tempNpmBin, \"npx\")); err == nil {\n\t\t\t\t\tutility.Rename(filepath.Join(tempNpmBin, \"npx\"), filepath.Join(root, \"v\"+version, \"npx\"))\n\t\t\t\t\tutility.Rename(filepath.Join(tempNpmBin, \"npx.cmd\"), filepath.Join(root, \"v\"+version, \"npx.cmd\"))\n\t\t\t\t}\n\n\t\t\t\tnpmSourcePath := filepath.Join(tempDir, \"nvm-npm\", \"npm-\"+npmv)\n\n\t\t\t\tif file.Exists(npmSourcePath) == false {\n\t\t\t\t\tnpmSourcePath = filepath.Join(tempDir, \"nvm-npm\", \"cli-\"+npmv)\n\t\t\t\t}\n\n\t\t\t\tmoveNpmErr := utility.Rename(npmSourcePath, filepath.Join(root, \"v\"+version, \"node_modules\", \"npm\"))\n\t\t\t\tif moveNpmErr != nil {\n\t\t\t\t\t// 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\n\t\t\t\t\tfor _, i := range [5]int{1, 2, 4, 8, 16} {\n\t\t\t\t\t\ttime.Sleep(time.Duration(i) * time.Second)\n\t\t\t\t\t\tmoveNpmErr = utility.Rename(npmSourcePath, filepath.Join(root, \"v\"+version, \"node_modules\", \"npm\"))\n\t\t\t\t\t\tif moveNpmErr == nil {\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif err == nil && moveNpmErr == nil {\n\t\t\t\t\terr = utility.Rename(filepath.Join(root, \"v\"+version), filepath.Join(env.root, \"v\"+version))\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tstatus <- Status{Err: err}\n\t\t\t\t\t}\n\t\t\t\t\tif show_progress {\n\t\t\t\t\t\tstatus <- Status{Done: true}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tstatus <- Status{Text: fmt.Sprintf(\"Installation complete. If you want to use this version, type\\n\\nnvm use %s\", version), Done: true}\n\t\t\t\t\t}\n\t\t\t\t} else if moveNpmErr != nil {\n\t\t\t\t\tstatus <- Status{Err: fmt.Errorf(\"Unable to move directory %s to node_modules: %v\", npmSourcePath, moveNpmErr), Done: true}\n\t\t\t\t} else {\n\t\t\t\t\tstatus <- Status{Err: fmt.Errorf(\"Failed to extract npm: %v\", err), Done: true}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\terr = utility.Rename(filepath.Join(root, \"v\"+version), filepath.Join(env.root, \"v\"+version))\n\t\t\t\tif err != nil {\n\t\t\t\t\tstatus <- Status{Err: err}\n\t\t\t\t}\n\n\t\t\t\tnpmurl := web.GetFullNpmUrl(version)\n\t\t\t\tif show_progress {\n\t\t\t\t\t// Send special error notification with link to npm release when it cannot be downloaded\n\t\t\t\t\tnotify(Notification{\n\t\t\t\t\t\tTitle:   \"Download Failure (npm)\",\n\t\t\t\t\t\tMessage: fmt.Sprintf(\"Please download npm v%s manually and extract to %s\\\\v%s\", version, env.root, version),\n\t\t\t\t\t\tIcon:    \"error\",\n\t\t\t\t\t\tActions: []Action{\n\t\t\t\t\t\t\t{Type: \"protocol\", Label: \"Manually Download\", URI: npmurl},\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t\tstatus <- Status{Done: true}\n\t\t\t\t} else {\n\t\t\t\t\tstatus <- 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}\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tstatus <- Status{Text: \"Version \" + version + \" is already installed.\", Done: true}\n\t\t}\n\t}()\n\n\t// Wait for the process to complete before exiting\n\twg.Wait()\n\tos.Exit(exitCode)\n}\n\nfunc reinstall(version, cpuarch string) {\n\t// Make sure a version is specified\n\tif len(version) == 0 {\n\t\tfmt.Println(\"Provide the version you want to uninstall.\")\n\t\thelp()\n\t\treturn\n\t}\n\n\tif strings.ToLower(version) == \"latest\" || strings.ToLower(version) == \"node\" {\n\t\tversion = getLatest()\n\t} else if strings.ToLower(version) == \"lts\" {\n\t\tversion = getLTS()\n\t} else if strings.ToLower(version) == \"newest\" {\n\t\tinstalled := node.GetInstalled(env.root)\n\t\tif len(installed) == 0 {\n\t\t\tfmt.Println(\"No versions of node.js found. Try installing the latest by typing nvm install latest.\")\n\t\t\treturn\n\t\t}\n\n\t\tversion = installed[0]\n\t}\n\n\tversion = cleanVersion(version)\n\n\t// Determine if the version exists and skip if it doesn't\n\tif node.IsVersionInstalled(env.root, version, \"32\") || node.IsVersionInstalled(env.root, version, \"64\") {\n\t\tv, _ := node.GetCurrentVersion()\n\n\t\tfmt.Printf(\"Removing v%v...\\n\", version)\n\n\t\tif v == version {\n\t\t\t// _, err := runElevated(fmt.Sprintf(`\"%s\" cmd /C rmdir \"%s\"`, filepath.Join(env.root, \"elevate.cmd\"), filepath.Clean(env.symlink)))\n\t\t\tabortOnBadSymlink(env.symlink)\n\t\t\t_, err := elevatedRun(\"rmdir\", filepath.Clean(env.symlink))\n\t\t\tif err != nil {\n\t\t\t\tfmt.Println(fmt.Sprint(err))\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\te := os.RemoveAll(filepath.Join(env.root, \"v\"+version))\n\t\tif e != nil {\n\t\t\tfmt.Printf(\"error: failed to remove v%v: %v\\n\", version, e)\n\t\t\tos.Exit(1)\n\t\t}\n\t} else {\n\t\tfmt.Printf(\"node v%v is not installed. Type \\\"nvm list\\\" to see what is installed.\\n\", version)\n\t}\n\n\tinstall(version, cpuarch)\n}\n\nfunc uninstall(version string) {\n\t// Make sure a version is specified\n\tif len(version) == 0 {\n\t\tfmt.Println(\"Provide the version you want to uninstall.\")\n\t\thelp()\n\t\treturn\n\t}\n\n\tif strings.ToLower(version) == \"latest\" || strings.ToLower(version) == \"node\" {\n\t\tversion = getLatest()\n\t} else if strings.ToLower(version) == \"lts\" {\n\t\tversion = getLTS()\n\t} else if strings.ToLower(version) == \"newest\" {\n\t\tinstalled := node.GetInstalled(env.root)\n\t\tif len(installed) == 0 {\n\t\t\tfmt.Println(\"No versions of node.js found. Try installing the latest by typing nvm install latest.\")\n\t\t\treturn\n\t\t}\n\n\t\tversion = installed[0]\n\t}\n\n\tversion = cleanVersion(version)\n\n\t// Determine if the version exists and skip if it doesn't\n\tif node.IsVersionInstalled(env.root, version, \"32\") || node.IsVersionInstalled(env.root, version, \"64\") || node.IsVersionInstalled(env.root, version, \"arm64\") {\n\t\tfmt.Printf(\"Uninstalling node v\" + version + \"...\")\n\t\tv, _ := node.GetCurrentVersion()\n\t\tif v == version {\n\t\t\t// _, err := runElevated(fmt.Sprintf(`\"%s\" cmd /C rmdir \"%s\"`, filepath.Join(env.root, \"elevate.cmd\"), filepath.Clean(env.symlink)))\n\t\t\tabortOnBadSymlink(env.symlink)\n\t\t\t_, err := elevatedRun(\"rmdir\", filepath.Clean(env.symlink))\n\t\t\tif err != nil {\n\t\t\t\tfmt.Println(fmt.Sprint(err))\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\te := os.RemoveAll(filepath.Join(env.root, \"v\"+version))\n\t\tif e != nil {\n\t\t\tfmt.Println(\"Error removing node v\" + version)\n\t\t\tfmt.Println(\"Manually remove \" + filepath.Join(env.root, \"v\"+version) + \".\")\n\t\t} else {\n\t\t\tfmt.Printf(\" done\")\n\t\t}\n\t} else {\n\t\tfmt.Println(\"node v\" + version + \" is not installed. Type \\\"nvm list\\\" to see what is installed.\")\n\t}\n\treturn\n}\n\nfunc versionNumberFrom(version string) string {\n\treg, _ := regexp.Compile(\"[^0-9]\")\n\n\tif reg.MatchString(version[:1]) {\n\t\tif version[0:1] != \"v\" {\n\t\t\turl := web.GetFullNodeUrl(\"latest-\" + version + \"/SHASUMS256.txt\")\n\t\t\tremoteContent, err := web.GetRemoteTextFile(url)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Println(err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\tcontent := strings.Split(remoteContent, \"\\n\")[0]\n\t\t\tif strings.Contains(content, \"node\") {\n\t\t\t\tparts := strings.Split(content, \"-\")\n\t\t\t\tif len(parts) > 1 {\n\t\t\t\t\tif parts[1][0:1] == \"v\" {\n\t\t\t\t\t\treturn parts[1][1:]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tfmt.Printf(\"\\\"%v\\\" is not a valid version or known alias.\\n\", version)\n\t\t\tfmt.Println(\"\\nAvailable aliases: latest, node (latest), lts\\nNamed releases (boron, dubnium, etc) are also supported.\")\n\t\t\tos.Exit(0)\n\t\t}\n\t}\n\n\tfor reg.MatchString(version[:1]) {\n\t\tversion = version[1:]\n\t}\n\n\treturn version\n}\n\nfunc splitVersion(version string) map[string]int {\n\tparts := strings.Split(version, \".\")\n\tvar result = make([]int, 3)\n\n\tfor i, item := range parts {\n\t\tv, _ := strconv.Atoi(item)\n\t\tresult[i] = v\n\t}\n\n\treturn map[string]int{\n\t\t\"major\": result[0],\n\t\t\"minor\": result[1],\n\t\t\"patch\": result[2],\n\t}\n}\n\nfunc findLatestSubVersion(version string, localOnly ...bool) string {\n\tif len(localOnly) > 0 && localOnly[0] {\n\t\tinstalled := node.GetInstalled(env.root)\n\t\tresult := \"\"\n\t\tfor _, v := range installed {\n\t\t\tif strings.HasPrefix(v, \"v\"+version) {\n\t\t\t\tif result != \"\" {\n\t\t\t\t\tcurrent, _ := semver.New(versionNumberFrom(result))\n\t\t\t\t\tnext, _ := semver.New(versionNumberFrom(v))\n\t\t\t\t\tif current.LT(*next) {\n\t\t\t\t\t\tresult = v\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tresult = v\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif len(strings.TrimSpace(result)) > 0 {\n\t\t\treturn versionNumberFrom(result)\n\t\t}\n\t}\n\n\tif len(strings.Split(version, \".\")) == 2 {\n\t\tall, _, _, _, _, _ := node.GetAvailable()\n\t\trequested := splitVersion(version + \".0\")\n\t\tfor _, v := range all {\n\t\t\tavailable := splitVersion(v)\n\t\t\tif requested[\"major\"] == available[\"major\"] {\n\t\t\t\tif requested[\"minor\"] == available[\"minor\"] {\n\t\t\t\t\tif available[\"patch\"] > requested[\"patch\"] {\n\t\t\t\t\t\trequested[\"patch\"] = available[\"patch\"]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif requested[\"minor\"] > available[\"minor\"] {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif requested[\"major\"] > available[\"major\"] {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\treturn fmt.Sprintf(\"%v.%v.%v\", requested[\"major\"], requested[\"minor\"], requested[\"patch\"])\n\t}\n\n\turl := web.GetFullNodeUrl(\"latest-v\" + version + \".x\" + \"/SHASUMS256.txt\")\n\tcontent, err := web.GetRemoteTextFile(url)\n\tif err != nil {\n\t\tif strings.Contains(err.Error(), \"HTTP Status 404\") {\n\t\t\tfmt.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)\n\t\t} else {\n\t\t\tfmt.Println(err)\n\t\t}\n\t\tos.Exit(1)\n\t}\n\tre := regexp.MustCompile(\"node-v(.+)+msi\")\n\treg := regexp.MustCompile(\"node-v|-[xa].+\")\n\tlatest := reg.ReplaceAllString(re.FindString(content), \"\")\n\treturn latest\n}\n\nfunc accessDenied(err error) bool {\n\tfmt.Println(fmt.Sprintf(\"%v\", err))\n\n\tif strings.Contains(strings.ToLower(err.Error()), \"access is denied\") {\n\t\tfmt.Println(\"See https://bit.ly/nvm4w-help\")\n\t\treturn true\n\t}\n\n\treturn false\n}\n\nfunc isSymlink(path string) (bool, error) {\n\tinfo, err := os.Lstat(path)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn info.Mode()&os.ModeSymlink != 0, nil\n}\n\nfunc use(version string, cpuarch string, reload ...bool) {\n\tversion, cpuarch, err := getVersion(version, cpuarch, true)\n\n\texitCode := 0\n\tstatus := make(chan Status)\n\twg := &sync.WaitGroup{}\n\twg.Add(1)\n\tnotifications := false\n\n\tif os.Args[len(os.Args)-1] == \"--notify\" {\n\t\tnotifications = true\n\t}\n\n\tgo func() {\n\t\tdefer func() {\n\t\t\tif notifications {\n\t\t\t\ttime.Sleep(1 * time.Second)\n\t\t\t}\n\t\t\twg.Done()\n\t\t}()\n\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase s := <-status:\n\t\t\t\tif s.Err != nil {\n\t\t\t\t\texitCode = 1\n\t\t\t\t\tif notifications {\n\t\t\t\t\t\t// Close progress dialog and send error notificaion\n\t\t\t\t\t\tnotify(Notification{\n\t\t\t\t\t\t\tTitle:   \"Node.js Activation Error\",\n\t\t\t\t\t\t\tMessage: fmt.Sprintf(\"nvm use %s failed because %v\", version, s.Err),\n\t\t\t\t\t\t\tIcon:    \"error\",\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\n\t\t\t\t\tfmt.Printf(\"activation error: %v\\n\", s.Err)\n\t\t\t\t\tif s.Help {\n\t\t\t\t\t\tfmt.Println(\" \")\n\t\t\t\t\t\thelp()\n\t\t\t\t\t}\n\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif s.Done {\n\t\t\t\t\tif s.Err == nil {\n\t\t\t\t\t\tif notifications {\n\t\t\t\t\t\t\tnotify(Notification{\n\t\t\t\t\t\t\t\tTitle:   \"Node.js Activated\",\n\t\t\t\t\t\t\t\tMessage: fmt.Sprintf(\"Your system is now configured to use v%s (%v-bit).\", version, cpuarch),\n\t\t\t\t\t\t\t\tIcon:    \"success\",\n\t\t\t\t\t\t\t\tActions: []Action{\n\t\t\t\t\t\t\t\t\t{Type: \"protocol\", Label: \"View Changelog\", URI: fmt.Sprintf(\"https://github.com/nodejs/node/releases/tag/v%s\", version)},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tfmt.Printf(\"Now using node v%s (%v-bit)\\n\", version, cpuarch)\n\t\t\t\t\t}\n\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif len(strings.TrimSpace(s.Text)) > 0 {\n\t\t\t\t\tfmt.Println(s.Text)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}()\n\n\ttime.Sleep(300 * time.Millisecond)\n\n\tgo func() {\n\t\tif err != nil {\n\t\t\tif !strings.Contains(err.Error(), \"No Major.Minor.Patch\") {\n\t\t\t\tstatus <- Status{Err: err, Done: true}\n\t\t\t}\n\t\t}\n\n\t\t// Check if a change is needed\n\t\tcurVersion, curCpuarch := node.GetCurrentVersion()\n\t\tif version == curVersion && cpuarch == curCpuarch {\n\t\t\tfmt.Println(\"node v\" + version + \" (\" + cpuarch + \"-bit) is already in use.\")\n\t\t\tstatus <- Status{Done: true}\n\t\t\treturn\n\t\t}\n\n\t\t// Make sure the version is installed. If not, warn.\n\t\tif !node.IsVersionInstalled(env.root, version, cpuarch) {\n\t\t\terr = fmt.Errorf(\"node v%s (%v-bit) is not installed.\", version, cpuarch)\n\t\t\tif notifications {\n\t\t\t\tstatus <- Status{Err: err, Done: true}\n\t\t\t}\n\t\t\tif (cpuarch == \"32\" && node.IsVersionInstalled(env.root, version, \"64\")) || (cpuarch == \"64\" && node.IsVersionInstalled(env.root, version, \"32\")) {\n\t\t\t\tstatus <- 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}\n\t\t\t}\n\t\t\tstatus <- Status{Err: fmt.Errorf(\"Version not installed. Run \\\"nvm ls\\\" to see available versions.\"), Done: true}\n\t\t}\n\n\t\t// Remove symlink if it already exists\n\t\tsym, _ := os.Lstat(env.symlink)\n\t\tif sym != nil {\n\t\t\terr = validSymlink(env.symlink)\n\t\t\tif err != nil {\n\t\t\t\tstatus <- Status{Err: err, Done: true}\n\t\t\t}\n\n\t\t\t// _, err := runElevated(fmt.Sprintf(`\"%s\" cmd /C rmdir \"%s\"`, filepath.Join(env.root, \"elevate.cmd\"), filepath.Clean(env.symlink)))\n\t\t\t_, err := elevatedRun(\"rmdir\", filepath.Clean(env.symlink))\n\t\t\tif err != nil {\n\t\t\t\tif accessDenied(err) {\n\t\t\t\t\tstatus <- Status{Err: err, Done: true}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Create new symlink\n\t\tvar ok bool\n\t\t// 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)))\n\t\tok, err = elevatedRun(\"mklink\", \"/D\", filepath.Clean(env.symlink), filepath.Join(env.root, \"v\"+version))\n\t\tif err != nil {\n\t\t\tif strings.Contains(err.Error(), \"not have sufficient privilege\") || strings.Contains(strings.ToLower(err.Error()), \"access is denied\") {\n\t\t\t\tok, err = elevatedRun(\"mklink\", \"/D\", filepath.Clean(env.symlink), filepath.Join(env.root, \"v\"+version))\n\t\t\t\tif err != nil {\n\t\t\t\t\tok = false\n\t\t\t\t\tstatus <- Status{Err: err, Done: true}\n\t\t\t\t\t// fmt.Println(fmt.Sprint(err)) // + \": \" + _stderr.String())\n\t\t\t\t} else {\n\t\t\t\t\tok = true\n\t\t\t\t}\n\t\t\t} else if strings.Contains(err.Error(), \"file already exists\") {\n\t\t\t\terr = validSymlink(env.symlink)\n\t\t\t\tif err != nil {\n\t\t\t\t\tstatus <- Status{Err: err, Done: true}\n\t\t\t\t}\n\n\t\t\t\tok, err = elevatedRun(\"rmdir\", filepath.Clean(env.symlink))\n\t\t\t\t// ok, err = runElevated(fmt.Sprintf(`\"%s\" cmd /C rmdir \"%s\"`, filepath.Join(env.root, \"elevate.cmd\"), filepath.Clean(env.symlink)))\n\t\t\t\treloadable := true\n\t\t\t\tif len(reload) > 0 {\n\t\t\t\t\treloadable = reload[0]\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\tstatus <- Status{Err: err, Done: true}\n\t\t\t\t} else if reloadable {\n\t\t\t\t\tuse(version, cpuarch, false)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tstatus <- Status{Err: err, Done: true}\n\t\t\t}\n\t\t}\n\t\tif !ok {\n\t\t\tstatus <- Status{Err: fmt.Errorf(\"failed to elevate permissions to create symlink\"), Done: true}\n\t\t}\n\n\t\t// Use the assigned CPU architecture\n\t\tcpuarch = arch.Validate(cpuarch)\n\t\tnodepath := filepath.Join(env.root, \"v\"+version, \"node.exe\")\n\t\tnode32path := filepath.Join(env.root, \"v\"+version, \"node32.exe\")\n\t\tnode64path := filepath.Join(env.root, \"v\"+version, \"node64.exe\")\n\t\tnode32exists := file.Exists(node32path)\n\t\tnode64exists := file.Exists(node64path)\n\t\tnodeexists := file.Exists(nodepath)\n\t\tif node32exists && cpuarch == \"32\" { // user wants 32, but node.exe is 64\n\t\t\tif nodeexists {\n\t\t\t\tutility.Rename(nodepath, node64path) // node.exe -> node64.exe\n\t\t\t}\n\t\t\tutility.Rename(node32path, nodepath) // node32.exe -> node.exe\n\t\t}\n\t\tif node64exists && cpuarch == \"64\" { // user wants 64, but node.exe is 32\n\t\t\tif nodeexists {\n\t\t\t\tutility.Rename(nodepath, node32path) // node.exe -> node32.exe\n\t\t\t}\n\t\t\tutility.Rename(node64path, nodepath) // node64.exe -> node.exe\n\t\t}\n\n\t\tstatus <- Status{Done: true}\n\t}()\n\n\t// Wait for the process to complete before exiting\n\twg.Wait()\n\tos.Exit(exitCode)\n}\n\nfunc abortOnBadSymlink(symlinkpath string) {\n\tif err := validSymlink(symlinkpath); err != nil {\n\t\tfmt.Printf(\"%v\\n\", err)\n\t\tos.Exit(1)\n\t}\n}\n\nfunc validSymlink(symlinkpath string) error {\n\tsymlinkpath = filepath.Clean(symlinkpath)\n\t// Prevent deletion if the symlink has been set to a physical directpry/file.\n\t// This isn't supposed to ever happen, but users have manually changed the settings.txt,\n\t// removing the physical file/directory unintentionally.\n\t// This is an anti-footgun.\n\tif symlink, err := isSymlink(symlinkpath); !symlink && err == nil {\n\t\treturn 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)\n\t}\n\n\treturn nil\n}\n\nfunc useArchitecture(a string) {\n\tif strings.ContainsAny(\"32\", os.Getenv(\"PROCESSOR_ARCHITECTURE\")) {\n\t\tfmt.Println(\"This computer only supports 32-bit processing.\")\n\t\treturn\n\t}\n\tif strings.Contains(\"arm64\", strings.ToLower(os.Getenv(\"PROCESSOR_ARCHITECTURE\"))) {\n\t\tfmt.Println(\"This computer only supports arm64-bit processing.\")\n\t\treturn\n\t}\n\tif a == \"32\" || a == \"64\" {\n\t\tenv.arch = a\n\t\tsaveSettings()\n\t\tfmt.Println(\"Set to \" + a + \"-bit mode\")\n\t} else {\n\t\tfmt.Println(\"Cannot set architecture to \" + a + \". Must be 32 or 64 are acceptable values.\")\n\t}\n}\n\nfunc list(listtype string) {\n\tif listtype == \"\" {\n\t\tlisttype = \"installed\"\n\t}\n\tif listtype != \"installed\" && listtype != \"available\" {\n\t\tfmt.Println(\"\\nInvalid list option.\\n\\nPlease use on of the following\\n  - nvm list\\n  - nvm list installed\\n  - nvm list available\")\n\t\thelp()\n\t\treturn\n\t}\n\n\tif listtype == \"installed\" {\n\t\tfmt.Println(\"\")\n\t\tinuse, a := node.GetCurrentVersion()\n\n\t\tv := node.GetInstalled(env.root)\n\n\t\tfor i := 0; i < len(v); i++ {\n\t\t\tversion := v[i]\n\t\t\tisnode, _ := regexp.MatchString(\"v\", version)\n\t\t\tstr := \"\"\n\t\t\tif isnode {\n\t\t\t\tif \"v\"+inuse == version {\n\t\t\t\t\tstr = str + \"  * \"\n\t\t\t\t} else {\n\t\t\t\t\tstr = str + \"    \"\n\t\t\t\t}\n\t\t\t\tstr = str + regexp.MustCompile(\"v\").ReplaceAllString(version, \"\")\n\t\t\t\tif \"v\"+inuse == version {\n\t\t\t\t\tstr = str + \" (Currently using \" + a + \"-bit executable)\"\n\t\t\t\t\t//            str = ansi.Color(str,\"green:black\")\n\t\t\t\t}\n\t\t\t\tfmt.Printf(str + \"\\n\")\n\t\t\t}\n\t\t}\n\t\tif len(v) == 0 {\n\t\t\tfmt.Println(\"No installations recognized.\")\n\t\t}\n\t} else {\n\t\t_, lts, current, stable, unstable, _ := node.GetAvailable()\n\n\t\treleases := 20\n\n\t\tdata := make([][]string, releases, releases+5)\n\t\tfor i := 0; i < releases; i++ {\n\t\t\trelease := make([]string, 4, 6)\n\n\t\t\trelease[0] = \"\"\n\t\t\trelease[1] = \"\"\n\t\t\trelease[2] = \"\"\n\t\t\trelease[3] = \"\"\n\n\t\t\tif len(current) > i {\n\t\t\t\tif len(current[i]) > 0 {\n\t\t\t\t\trelease[0] = current[i]\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif len(lts) > i {\n\t\t\t\tif len(lts[i]) > 0 {\n\t\t\t\t\trelease[1] = lts[i]\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif len(stable) > i {\n\t\t\t\tif len(stable[i]) > 0 {\n\t\t\t\t\trelease[2] = stable[i]\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif len(unstable) > i {\n\t\t\t\tif len(unstable[i]) > 0 {\n\t\t\t\t\trelease[3] = unstable[i]\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tdata[i] = release\n\t\t}\n\n\t\tfmt.Println(\"\")\n\t\ttable := tablewriter.NewWriter(os.Stdout)\n\t\ttable.SetHeader([]string{\"   Current  \", \"    LTS     \", \" Old Stable \", \"Old Unstable\"})\n\t\ttable.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false})\n\t\ttable.SetAlignment(tablewriter.ALIGN_CENTER)\n\t\ttable.SetCenterSeparator(\"|\")\n\t\ttable.AppendBulk(data) // Add Bulk Data\n\t\ttable.Render()\n\n\t\tfmt.Println(\"\\nThis is a partial list. For a complete list, visit https://nodejs.org/en/download/releases\")\n\t}\n}\n\nfunc enable() {\n\tdir := \"\"\n\tfiles, _ := ioutil.ReadDir(env.root)\n\tfor _, f := range files {\n\t\tif f.IsDir() {\n\t\t\tisnode, _ := regexp.MatchString(\"v\", f.Name())\n\t\t\tif isnode {\n\t\t\t\tdir = f.Name()\n\t\t\t}\n\t\t}\n\t}\n\tfmt.Println(\"nvm enabled\")\n\tif dir != \"\" {\n\t\tuse(strings.Trim(regexp.MustCompile(\"v\").ReplaceAllString(dir, \"\"), \" \\n\\r\"), env.arch)\n\t} else {\n\t\tfmt.Println(\"No versions of node.js found. Try installing the latest by typing nvm install latest\")\n\t}\n}\n\nfunc disable() {\n\t// ok, err := runElevated(fmt.Sprintf(`\"%s\" cmd /C rmdir \"%s\"`, filepath.Join(env.root, \"elevate.cmd\"), filepath.Clean(env.symlink)))\n\tabortOnBadSymlink(env.symlink)\n\tok, err := elevatedRun(\"rmdir\", filepath.Clean(env.symlink))\n\tif !ok {\n\t\treturn\n\t}\n\tif err != nil {\n\t\tfmt.Print(fmt.Sprint(err))\n\t}\n\n\tfmt.Println(\"nvm disabled\")\n}\n\nconst (\n\tVER_PLATFORM_WIN32s        = 0\n\tVER_PLATFORM_WIN32_WINDOWS = 1\n\tVER_PLATFORM_WIN32_NT      = 2\n)\n\ntype OSVersionInfoEx struct {\n\tOSVersionInfoSize uint32\n\tMajorVersion      uint32\n\tMinorVersion      uint32\n\tBuildNumber       uint32\n\tPlatformId        uint32\n\tCSDVersion        [128]uint16\n}\n\nfunc checkLocalEnvironment() {\n\tproblems := make([]string, 0)\n\n\t// Check for PATH problems\n\tpaths := strings.Split(os.Getenv(\"PATH\"), \";\")\n\tcurrent := env.symlink\n\tif strings.HasSuffix(current, \"/\") || strings.HasSuffix(current, \"\\\\\") {\n\t\tcurrent = current[:len(current)-1]\n\t}\n\n\tnvmsymlinkfound := false\n\tfor _, path := range paths {\n\t\tif strings.HasSuffix(path, \"/\") || strings.HasSuffix(path, \"\\\\\") {\n\t\t\tpath = path[:len(path)-1]\n\t\t}\n\n\t\tif strings.EqualFold(path, current) {\n\t\t\tnvmsymlinkfound = true\n\t\t\tbreak\n\t\t}\n\n\t\tif _, err := os.Stat(filepath.Join(path, \"node.exe\")); err == nil {\n\t\t\tproblems = 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+\"\\\".\")\n\t\t\tbreak\n\t\t} else if !errors.Is(err, os.ErrNotExist) {\n\t\t\tfmt.Println(\"Error running environment check:\\n\" + err.Error())\n\t\t}\n\t}\n\n\t// Check for developer mode\n\tdevmode := \"OFF\"\n\tk, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock`, registry.QUERY_VALUE)\n\tif err == nil {\n\t\tvalue, _, err := k.GetIntegerValue(\"AllowDevelopmentWithoutDevLicense\")\n\t\tif err == nil {\n\t\t\tif value > 0 {\n\t\t\t\tdevmode = \"ON\"\n\t\t\t}\n\t\t} else {\n\t\t\tdevmode = \"UNKNOWN\"\n\t\t}\n\t} else {\n\t\tdevmode = \"UNKNOWN (user cannot read registry)\"\n\t}\n\tdefer k.Close()\n\n\t// Check for permission problems\n\tadmin, elevated, err := getProcessPermissions()\n\tif err == nil {\n\t\tif !admin && !elevated {\n\t\t\tuser, _ := user.Current()\n\t\t\tusername := strings.Split(user.Username, \"\\\\\")\n\t\t\tfmt.Printf(\"%v is not using admin or elevated rights\", username[len(username)-1])\n\t\t\tif devmode == \"ON\" {\n\t\t\t\tfmt.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)\n\t\t\t} else {\n\t\t\t\tfmt.Println(\".\")\n\t\t\t}\n\t\t} else {\n\t\t\tif admin {\n\t\t\t\tfmt.Println(\"Running NVM for Windows with administrator privileges.\")\n\t\t\t} else if elevated {\n\t\t\t\tfmt.Println(\"Running NVM for Windows with elevated permissions.\")\n\t\t\t}\n\t\t}\n\t} else {\n\t\tfmt.Println(err)\n\t}\n\n\tkernel32 := syscall.NewLazyDLL(\"kernel32.dll\")\n\thandle, _, err := kernel32.NewProc(\"GetStdHandle\").Call(uintptr(0xfffffff5)) // get handle for console input\n\tif err != nil && err.Error() != \"The operation completed successfully.\" {\n\t\tfmt.Printf(\"Error getting console handle: %v\", err)\n\t} else {\n\t\tvar mode uint32\n\t\tresult, _, _ := kernel32.NewProc(\"GetConsoleMode\").Call(handle, uintptr(unsafe.Pointer(&mode)))\n\t\tif result != 0 {\n\t\t\tvar title [256]uint16\n\t\t\t_, _, err := kernel32.NewProc(\"GetConsoleTitleW\").Call(uintptr(unsafe.Pointer(&title)), uintptr(len(title)))\n\t\t\tif err != nil && err.Error() != \"The operation completed successfully.\" {\n\t\t\t\tfmt.Printf(\"Error getting console title: %v\", err)\n\t\t\t} else {\n\t\t\t\tconsoleTitle := syscall.UTF16ToString(title[:])\n\n\t\t\t\tif !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\") {\n\t\t\t\t\tproblems = 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))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Get Windows details\n\tgetVersionEx := kernel32.NewProc(\"GetVersionExW\")\n\tversionInfo := OSVersionInfoEx{\n\t\tOSVersionInfoSize: uint32(unsafe.Sizeof(OSVersionInfoEx{})),\n\t}\n\tret, _, _ := getVersionEx.Call(uintptr(unsafe.Pointer(&versionInfo)))\n\tif ret == 0 {\n\t\tfmt.Println(\"Failed to retrieve version information.\")\n\t}\n\t// fmt.Printf(\" %d.%d\\n\", versionInfo.MajorVersion, versionInfo.MinorVersion)\n\tmaj, min, patch := windows.RtlGetNtVersionNumbers()\n\tfmt.Printf(\"\\nWindows Version:        %d.%d (Build %d)\\n\", maj, min, patch)\n\n\t// SHELL in Linux\n\t// TERM in Windows\n\t// COMSPEC in Windows provides the terminal path\n\t// shell := os.Getenv(\"ConEmuANSI\")\n\t// fmt.Printf(\"Shell: %v\\n\", shell)\n\n\t// Display developer mode status\n\tif !admin {\n\t\t// if devmode == \"ON\" {\n\t\t// \tdevmode = color.GreenString(devmode)\n\t\t// } else if devmode == \"OFF\" {\n\t\t// \tdevmode = color.YellowString(devmode)\n\t\t// } else {\n\t\t// \tdevmode = color.YellowString(devmode)\n\t\t// }\n\n\t\tfmt.Printf(\"\\n%v %v\\n\", \"Windows Developer Mode:\", devmode)\n\t}\n\n\texecutable := os.Args[0]\n\tpath, err := where.Find(filepath.Base(executable))\n\n\tif err != nil {\n\t\tpath = \"UNKNOWN: \" + err.Error()\n\t}\n\n\tout := \"none\\n(run \\\"nvm use <version>\\\" to activate a version)\\n\"\n\toutput, err := exec.Command(os.Getenv(\"NVM_SYMLINK\")+\"\\\\node.exe\", \"-v\").Output()\n\tif err == nil {\n\t\tout = string(output)\n\t}\n\n\tv := node.GetInstalled(env.root)\n\n\t// Make sure author-nvm.exe is available and runs\n\texe, _ := os.Executable()\n\tvalueBytes, err := exec.Command(exe, \"author\", \"version\").Output()\n\tauthorNvmVersion := \"Not Detected\"\n\tif err != nil {\n\t\t// fmt.Println(\"Error running author-nvm.exe: \" + err.Error())\n\t\tproblems = append(problems, \"The author-nvm.exe file is missing or not executable.\")\n\t} else {\n\t\tauthorNvmVersion = strings.TrimSpace(string(valueBytes))\n\t}\n\n\tnvmhome := os.Getenv(\"NVM_HOME\")\n\tmirrors := \"No mirrors configured\"\n\tif len(env.node_mirror) > 0 && len(env.npm_mirror) > 0 {\n\t\tmirrors = env.node_mirror + \" (node) and \" + env.npm_mirror + \" (npm)\"\n\t} else if len(env.node_mirror) > 0 {\n\t\tmirrors = env.node_mirror + \" (node)\"\n\t} else if len(env.npm_mirror) > 0 {\n\t\tmirrors = env.npm_mirror + \" (npm)\"\n\t}\n\tfmt.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)\n\n\tif !nvmsymlinkfound {\n\t\tproblems = append(problems, \"The NVM4W symlink (\"+env.symlink+\") was not found in the PATH environment variable.\")\n\t}\n\n\tif home == symlink {\n\t\tproblems = append(problems, \"NVM_HOME and NVM_SYMLINK cannot be the same value (\"+symlink+\"). Change NVM_SYMLINK.\")\n\t}\n\n\tfileInfo, err := os.Lstat(symlink)\n\tif err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\tfmt.Println(\"NVM_SYMLINK does not exist yet. This is auto-created when \\\"nvm use\\\" is run.\")\n\t\t} else {\n\t\t\tproblems = append(problems, \"Could not determine if NVM_SYMLINK is actually a symlink: \"+err.Error())\n\t\t}\n\t} else {\n\t\tif fileInfo.Mode()&os.ModeSymlink != 0 {\n\t\t\ttargetPath, err := os.Readlink(symlink)\n\t\t\tif err != nil {\n\t\t\t\tproblems = append(problems, fmt.Sprintf(\"SYMLINK_READ Error: %v\", err))\n\t\t\t} else {\n\t\t\t\ttargetFileInfo, err := os.Lstat(targetPath)\n\t\t\t\tif err != nil {\n\t\t\t\t\tproblems = append(problems, fmt.Sprintf(\"SYMLINK_READ Error: %v\", err))\n\t\t\t\t} else if !targetFileInfo.Mode().IsDir() {\n\t\t\t\t\tproblems = append(problems, \"NVM_SYMLINK is a symlink linking to a file instead of a directory.\")\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tproblems = append(problems, \"NVM_SYMLINK (\"+symlink+\") is not a valid symlink.\")\n\t\t}\n\t}\n\n\tif strings.Contains(symlink, home) {\n\t\tproblems = 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.\")\n\t}\n\n\tipv6, err := web.IsLocalIPv6()\n\tif err != nil {\n\t\tproblems = append(problems, \"Connection type cannot be determined: \"+err.Error())\n\t} else if !ipv6 {\n\t\tfmt.Println(\"\\nIPv6 is enabled. This has been known to slow downloads significantly.\")\n\t}\n\n\tnodelist := web.Ping(web.GetFullNodeUrl(\"index.json\"))\n\tif !nodelist {\n\t\tif len(env.node_mirror) > 0 && env.node_mirror != \"none\" {\n\t\t\tproblems = append(problems, \"Connection to \"+env.node_mirror+\" (mirror) cannot be established. Check the mirror server to assure it is online.\")\n\t\t} else {\n\t\t\tif len(env.proxy) > 0 {\n\t\t\t\tproblems = append(problems, \"Connection to nodejs.org cannot be established. Check your proxy (\"+env.proxy+\") and your physical internet connection.\")\n\t\t\t} else {\n\t\t\t\tproblems = append(problems, \"Connection to nodejs.org cannot be established. Check your internet connection.\")\n\t\t\t}\n\t\t}\n\t}\n\n\tinvalid := make([]string, 0)\n\tinvalidnpm := make([]string, 0)\n\tfor i := 0; i < len(v); i++ {\n\t\tif _, err = os.Stat(filepath.Join(env.root, v[i], \"node.exe\")); err != nil {\n\t\t\tinvalid = append(invalid, v[i])\n\t\t} else if _, err = os.Stat(filepath.Join(env.root, v[i], \"npm.cmd\")); err != nil {\n\t\t\tfmt.Println(err)\n\t\t\tinvalidnpm = append(invalid, v[i])\n\t\t}\n\t}\n\n\tif len(invalid) > 0 {\n\t\tproblems = append(problems, \"The following Node installations are invalid (missing node.exe): \"+strings.Join(invalid, \", \")+\" - consider reinstalling these versions\")\n\t}\n\n\tif len(invalidnpm) > 0 {\n\t\tfmt.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, \", \"))\n\t}\n\n\tif len(env.npm_mirror) > 0 {\n\t\tfmt.Println(\"If you are experiencing npm problems, check the npm mirror (\" + env.npm_mirror + \") to assure it is online and accessible.\")\n\t}\n\n\tif _, err := os.Stat(env.settings); err != nil {\n\t\tproblems = append(problems, \"Cannot find \"+env.settings)\n\t}\n\n\tif len(problems) == 0 {\n\t\tfmt.Println(\"\\n\" + \"No problems detected.\")\n\t} else {\n\t\tfmt.Println(\"\\nPROBLEMS DETECTED\\n-----------------\")\n\t\tfor _, p := range problems {\n\t\t\tfmt.Println(p + \"\\n\")\n\t\t}\n\t}\n\n\t// Check for updates\n\tcolorize := true\n\tif err := upgrade.EnableVirtualTerminalProcessing(); err != nil {\n\t\tcolorize = false\n\t}\n\tupdate, checkerr := upgrade.Get()\n\n\tif checkerr == nil {\n\t\tif len(update.Warnings) > 0 {\n\t\t\tfmt.Println(\"\")\n\t\t}\n\t\tfor _, warning := range update.Warnings {\n\t\t\tupgrade.Warn(warning, colorize)\n\t\t}\n\t\tfor _, warning := range update.VersionWarnings {\n\t\t\tupgrade.Warn(warning, colorize)\n\t\t}\n\t\tif len(update.Warnings) > 0 || len(update.VersionWarnings) > 0 {\n\t\t\tfmt.Println(\"\")\n\t\t}\n\t}\n\n\tif checkerr != nil {\n\t\tfmt.Println(\"error checking for updates: \" + checkerr.Error())\n\t} else {\n\t\tnewVersion, available, err := update.Available(NvmVersion)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"Error checking for updates: \" + err.Error())\n\t\t} else if available {\n\t\t\tupgrade.Warn(fmt.Sprintf(\"An upgrade is available: v%s\", newVersion), colorize)\n\t\t\tfmt.Println(\"   run \\\"nvm upgrade\\\" to update.\\n\")\n\t\t}\n\t}\n\n\tfmt.Println(\"\\n\" + \"Find help at https://github.com/coreybutler/nvm-windows/wiki/Common-Issues\")\n}\n\nfunc help() {\n\tfmt.Println(\"\\nRunning version \" + NvmVersion + \".\")\n\tfmt.Println(\"\\nUsage:\")\n\tfmt.Println(\" \")\n\tfmt.Println(\"  nvm arch                     : Show if node is running in 32 or 64 bit mode.\")\n\tfmt.Println(\"  nvm current                  : Display active version.\")\n\tfmt.Println(\"  nvm debug                    : Check the NVM4W process for known problems (troubleshooter).\")\n\tfmt.Println(\"  nvm install <version> [arch] : The version can be a specific version, \\\"latest\\\" for the latest current version, or \\\"lts\\\" for the\")\n\tfmt.Println(\"                                 most recent LTS version. Optionally specify whether to install the 32 or 64 bit version (defaults\")\n\tfmt.Println(\"                                 to system arch). Set [arch] to \\\"all\\\" to install 32 AND 64 bit versions.\")\n\tfmt.Println(\"                                 Add --insecure to the end of this command to bypass SSL validation of the remote download server.\")\n\tfmt.Println(\"  nvm list [available]         : List the node.js installations. Type \\\"available\\\" at the end to see what can be installed. Aliased as ls.\")\n\tfmt.Println(\"  nvm on                       : Enable node.js version management.\")\n\tfmt.Println(\"  nvm off                      : Disable node.js version management.\")\n\tfmt.Println(\"  nvm proxy [url]              : Set a proxy to use for downloads. Leave [url] blank to see the current proxy.\")\n\tfmt.Println(\"                                 Set [url] to \\\"none\\\" to remove the proxy.\")\n\tfmt.Println(\"  nvm node_mirror [url]        : Set the node mirror. Defaults to https://nodejs.org/dist/. Leave [url] blank to use default url.\")\n\tfmt.Println(\"  nvm npm_mirror [url]         : Set the npm mirror. Defaults to https://github.com/npm/cli/archive/. Leave [url] blank to default url.\")\n\tfmt.Println(\"  nvm uninstall <version>      : The version must be a specific version.\")\n\tfmt.Println(\"  nvm upgrade                  : Update nvm to the latest version. Manual rollback available for 7 days after upgrade.\")\n\tfmt.Println(\"  nvm use [version] [arch]     : Switch to use the specified version. Optionally use \\\"latest\\\", \\\"lts\\\", or \\\"newest\\\".\")\n\tfmt.Println(\"                                 \\\"newest\\\" is the latest installed version. Optionally specify 32/64bit architecture.\")\n\tfmt.Println(\"                                 nvm use <arch> will continue using the selected version, but switch to 32/64 bit mode.\")\n\tfmt.Println(\"  nvm reinstall <version>      : A shortcut method to clean and reinstall a specific version.\")\n\tfmt.Println(\"  nvm root [path]              : Set the directory where nvm should store different versions of node.js.\")\n\tfmt.Println(\"                                 If <path> is not set, the current root will be displayed.\")\n\tfmt.Println(\"  nvm subscribe [--]<topic>    : Subscribe to desktop notifications.\")\n\tfmt.Println(\"                                 Valid topics: lts, current, nvm4w, author\")\n\tfmt.Println(\"  nvm unsubscribe [--]<topic>  : Unsubscribe from desktop notifications.\")\n\tfmt.Println(\"                                 Valid topics: lts, current, nvm4w, author\")\n\tfmt.Println(\"  nvm [--]version              : Displays the current running version of nvm for Windows. Aliased as v.\")\n\tfmt.Println(\" \")\n}\n\n// ===============================================================\n// END | CLI functions\n// ===============================================================\n\n// ===============================================================\n// BEGIN | Utility functions\n// ===============================================================\nfunc checkVersionExceedsLatest(version string) bool {\n\t//content := web.GetRemoteTextFile(\"http://nodejs.org/dist/latest/SHASUMS256.txt\")\n\turl := web.GetFullNodeUrl(\"latest/SHASUMS256.txt\")\n\tcontent, err := web.GetRemoteTextFile(url)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\tos.Exit(1)\n\t}\n\tre := regexp.MustCompile(\"node-v(.+)+msi\")\n\treg := regexp.MustCompile(\"node-v|-[xa].+\")\n\tlatest := reg.ReplaceAllString(re.FindString(content), \"\")\n\tvar vArr = strings.Split(version, \".\")\n\tvar lArr = strings.Split(latest, \".\")\n\tfor index := range lArr {\n\t\tlat, _ := strconv.Atoi(lArr[index])\n\t\tver, _ := strconv.Atoi(vArr[index])\n\t\t//Should check for valid input (checking for conversion errors) but this tool is made to trust the user\n\t\tif ver < lat {\n\t\t\treturn false\n\t\t} else if ver > lat {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc cleanVersion(version string) string {\n\tre := regexp.MustCompile(\"\\\\d+.\\\\d+.\\\\d+\")\n\tmatched := re.FindString(version)\n\n\tif len(matched) == 0 {\n\t\tre = regexp.MustCompile(\"\\\\d+.\\\\d+\")\n\t\tmatched = re.FindString(version)\n\t\tif len(matched) == 0 {\n\t\t\tmatched = version + \".0.0\"\n\t\t} else {\n\t\t\tmatched = matched + \".0\"\n\t\t}\n\t\tfmt.Println(matched)\n\t}\n\n\treturn matched\n}\n\n// Given a node.js version, returns the associated npm version\nfunc getNpmVersion(nodeversion string) string {\n\t_, _, _, _, _, npm := node.GetAvailable()\n\tif len(npm) == 0 {\n\t\tfmt.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.\")\n\t\tos.Exit(0)\n\t}\n\treturn npm[nodeversion]\n}\n\nfunc getLatest() string {\n\turl := web.GetFullNodeUrl(\"latest/SHASUMS256.txt\")\n\tcontent, err := web.GetRemoteTextFile(url)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\tos.Exit(1)\n\t}\n\tre := regexp.MustCompile(\"node-v(.+)+msi\")\n\treg := regexp.MustCompile(\"node-v|-[xa].+\")\n\treturn reg.ReplaceAllString(re.FindString(content), \"\")\n}\n\nfunc getLTS() string {\n\t// all, ltsList, current, stable, unstable, npm := node.GetAvailable()\n\t// fmt.Println(all)\n\t// fmt.Println(ltsList)\n\t// fmt.Println(current)\n\t// fmt.Println(stable)\n\t// fmt.Println(unstable)\n\t// fmt.Println(npm)\n\t_, ltsList, _, _, _, _ := node.GetAvailable()\n\n\tif len(ltsList) == 0 {\n\t\tfmt.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.\")\n\t\tos.Exit(0)\n\t}\n\n\t// ltsList has already been numerically sorted\n\treturn ltsList[0]\n}\n\nfunc updateRootDir(path string) {\n\t_, err := os.Stat(path)\n\tif err != nil {\n\t\tfmt.Println(path + \" does not exist or could not be found.\")\n\t\treturn\n\t}\n\n\tcurrentRoot := env.root\n\tenv.root = filepath.Clean(path)\n\n\t// Copy command files\n\tos.Link(filepath.Clean(currentRoot+\"/elevate.cmd\"), filepath.Clean(env.root+\"/elevate.cmd\"))\n\tos.Link(filepath.Clean(currentRoot+\"/elevate.vbs\"), filepath.Clean(env.root+\"/elevate.vbs\"))\n\n\tsaveSettings()\n\n\tif currentRoot != env.root {\n\t\tfmt.Println(\"\\nRoot has been changed from \" + currentRoot + \" to \" + path)\n\t}\n}\n\nfunc elevatedRun(name string, arg ...string) (bool, error) {\n\tok, err := run(\"cmd\", nil, append([]string{\"/C\", name}, arg...)...)\n\tif err != nil {\n\t\texe, _ := os.Executable()\n\t\tcmd := filepath.Join(filepath.Dir(exe), \"elevate.cmd\")\n\t\tok, err = run(cmd, &env.root, append([]string{\"cmd\", \"/C\", name}, arg...)...)\n\t}\n\n\treturn ok, err\n}\n\nfunc run(name string, dir *string, arg ...string) (bool, error) {\n\tc := exec.Command(name, arg...)\n\tif dir != nil {\n\t\tc.Dir = *dir\n\t}\n\tvar stderr bytes.Buffer\n\tc.Stderr = &stderr\n\terr := c.Run()\n\tif err != nil {\n\t\treturn false, errors.New(fmt.Sprint(err) + \": \" + stderr.String())\n\t}\n\n\treturn true, nil\n}\n\nfunc runElevated(command string, forceUAC ...bool) (bool, error) {\n\tuac := true //false\n\tif len(forceUAC) > 0 {\n\t\tuac = forceUAC[0]\n\t}\n\n\tif uac {\n\t\t// Alternative elevation option at stackoverflow.com/questions/31558066/how-to-ask-for-administer-privileges-on-windows-with-go\n\t\tcmd := exec.Command(filepath.Join(env.root, \"elevate.cmd\"), command)\n\n\t\tvar output bytes.Buffer\n\t\tvar _stderr bytes.Buffer\n\t\tcmd.Stdout = &output\n\t\tcmd.Stderr = &_stderr\n\t\tperr := cmd.Run()\n\t\tif perr != nil {\n\t\t\treturn false, errors.New(fmt.Sprint(perr) + \": \" + _stderr.String())\n\t\t}\n\t}\n\n\tc := exec.Command(\"cmd\") // dummy executable that actually needs to exist but we'll overwrite using .SysProcAttr\n\n\t// Based on the official docs, syscall.SysProcAttr.CmdLine doesn't exist.\n\t// But it does and is vital:\n\t// https://github.com/golang/go/issues/15566#issuecomment-333274825\n\t// https://medium.com/@felixge/killing-a-child-process-and-all-of-its-children-in-go-54079af94773\n\tc.SysProcAttr = &syscall.SysProcAttr{CmdLine: command}\n\n\tvar stderr bytes.Buffer\n\tc.Stderr = &stderr\n\n\terr := c.Run()\n\tif err != nil {\n\t\tmsg := stderr.String()\n\t\tif strings.Contains(msg, \"not have sufficient privilege\") && uac {\n\t\t\treturn runElevated(command, false)\n\t\t}\n\t\t// fmt.Println(fmt.Sprint(err) + \": \" + stderr.String())\n\t\treturn false, errors.New(fmt.Sprint(err) + \": \" + msg)\n\t}\n\n\treturn true, nil\n}\n\nfunc saveSettings() {\n\tcontent := \"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\")\n\tcontent = content + \"\\r\\nnode_mirror: \" + strings.Trim(encode(env.node_mirror), \" \\n\\r\") + \"\\r\\nnpm_mirror: \" + strings.Trim(encode(env.npm_mirror), \" \\n\\r\")\n\tioutil.WriteFile(env.settings, []byte(content), 0644)\n\tos.Setenv(\"NVM_HOME\", strings.Trim(encode(env.root), \" \\n\\r\"))\n}\n\nfunc getProcessPermissions() (admin bool, elevated bool, err error) {\n\tadmin = false\n\televated = false\n\tvar sid *windows.SID\n\terr = windows.AllocateAndInitializeSid(\n\t\t&windows.SECURITY_NT_AUTHORITY,\n\t\t2,\n\t\twindows.SECURITY_BUILTIN_DOMAIN_RID,\n\t\twindows.DOMAIN_ALIAS_RID_ADMINS,\n\t\t0, 0, 0, 0, 0, 0,\n\t\t&sid)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer windows.FreeSid(sid)\n\n\ttoken := windows.Token(0)\n\televated = token.IsElevated()\n\tadmin, err = token.IsMember(sid)\n\n\treturn\n}\n\nfunc encode(val string) string {\n\tconverted := encoding.ToUTF8(val)\n\t// if err != nil {\n\t// \tfmt.Printf(\"WARNING: [encoding error] - %v\\n\", err.Error())\n\t// \treturn val\n\t// }\n\n\treturn string(converted)\n}\n\n// ===============================================================\n// END | Utility functions\n// ===============================================================\n\nfunc setup() {\n\tlines, err := file.ReadLines(env.settings)\n\tif err != nil {\n\t\tfmt.Println(\"\\nERROR\", err)\n\t\tos.Exit(1)\n\t}\n\n\t// Process each line and extract the value\n\tm := make(map[string]string)\n\tfor _, line := range lines {\n\t\tline = strings.TrimSpace(line)\n\t\tline = os.ExpandEnv(line)\n\t\tres := strings.Split(line, \":\")\n\t\tif len(res) < 2 {\n\t\t\tcontinue\n\t\t}\n\t\tm[res[0]] = strings.TrimSpace(strings.Join(res[1:], \":\"))\n\t}\n\n\tif val, ok := m[\"root\"]; ok {\n\t\tenv.root = filepath.Clean(val)\n\t}\n\tif val, ok := m[\"originalpath\"]; ok {\n\t\tenv.originalpath = filepath.Clean(val)\n\t}\n\tif val, ok := m[\"originalversion\"]; ok {\n\t\tenv.originalversion = val\n\t}\n\tif val, ok := m[\"arch\"]; ok {\n\t\tenv.arch = val\n\t}\n\tif val, ok := m[\"node_mirror\"]; ok {\n\t\tenv.node_mirror = val\n\t}\n\tif val, ok := m[\"npm_mirror\"]; ok {\n\t\tenv.npm_mirror = val\n\t}\n\n\tif val, ok := m[\"proxy\"]; ok {\n\t\tif val != \"none\" && val != \"\" {\n\t\t\tif strings.ToLower(val[0:4]) != \"http\" {\n\t\t\t\tval = \"http://\" + val\n\t\t\t}\n\t\t\tres, err := url.Parse(val)\n\t\t\tif err == nil {\n\t\t\t\tweb.SetProxy(res.String(), env.verifyssl)\n\t\t\t\tenv.proxy = res.String()\n\t\t\t}\n\t\t}\n\t}\n\n\tweb.SetMirrors(env.node_mirror, env.npm_mirror)\n\tenv.arch = arch.Validate(env.arch)\n\n\t// Make sure the directories exist\n\t_, e := os.Stat(env.root)\n\tif e != nil {\n\t\tfmt.Println(env.root + \" could not be found or does not exist. Exiting.\")\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "src/semver/semver.go",
    "content": "/**\n * Used under the MIT License.\n * Semver courtesy Benedikt Lang (https://github.com/blang)\n */\npackage semver\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n)\n\nconst (\n\tnumbers  string = \"0123456789\"\n\talphas          = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-\"\n\talphanum        = alphas + numbers\n\tdot             = \".\"\n\thyphen          = \"-\"\n\tplus            = \"+\"\n)\n\n// Latest fully supported spec version\nvar SPEC_VERSION = Version{\n\tMajor: 2,\n\tMinor: 0,\n\tPatch: 0,\n}\n\ntype Version struct {\n\tMajor uint64\n\tMinor uint64\n\tPatch uint64\n\tPre   []*PRVersion\n\tBuild []string //No Precedence\n}\n\n// Version to string\nfunc (v *Version) String() string {\n\tversionArray := []string{\n\t\tstrconv.FormatUint(v.Major, 10),\n\t\tdot,\n\t\tstrconv.FormatUint(v.Minor, 10),\n\t\tdot,\n\t\tstrconv.FormatUint(v.Patch, 10),\n\t}\n\tif len(v.Pre) > 0 {\n\t\tversionArray = append(versionArray, hyphen)\n\t\tfor i, pre := range v.Pre {\n\t\t\tif i > 0 {\n\t\t\t\tversionArray = append(versionArray, dot)\n\t\t\t}\n\t\t\tversionArray = append(versionArray, pre.String())\n\t\t}\n\t}\n\tif len(v.Build) > 0 {\n\t\tversionArray = append(versionArray, plus, strings.Join(v.Build, dot))\n\t}\n\treturn strings.Join(versionArray, \"\")\n}\n\n// Checks if v is greater than o.\nfunc (v *Version) GT(o *Version) bool {\n\treturn (v.Compare(o) == 1)\n}\n\n// Checks if v is greater than or equal to o.\nfunc (v *Version) GTE(o *Version) bool {\n\treturn (v.Compare(o) >= 0)\n}\n\n// Checks if v is less than o.\nfunc (v *Version) LT(o *Version) bool {\n\treturn (v.Compare(o) == -1)\n}\n\n// Checks if v is less than or equal to o.\nfunc (v *Version) LTE(o *Version) bool {\n\treturn (v.Compare(o) <= 0)\n}\n\n// Compares Versions v to o:\n// -1 == v is less than o\n// 0 == v is equal to o\n// 1 == v is greater than o\nfunc (v *Version) Compare(o *Version) int {\n\tif v.Major != o.Major {\n\t\tif v.Major > o.Major {\n\t\t\treturn 1\n\t\t} else {\n\t\t\treturn -1\n\t\t}\n\t}\n\tif v.Minor != o.Minor {\n\t\tif v.Minor > o.Minor {\n\t\t\treturn 1\n\t\t} else {\n\t\t\treturn -1\n\t\t}\n\t}\n\tif v.Patch != o.Patch {\n\t\tif v.Patch > o.Patch {\n\t\t\treturn 1\n\t\t} else {\n\t\t\treturn -1\n\t\t}\n\t}\n\n\t// Quick comparison if a version has no prerelease versions\n\tif len(v.Pre) == 0 && len(o.Pre) == 0 {\n\t\treturn 0\n\t} else if len(v.Pre) == 0 && len(o.Pre) > 0 {\n\t\treturn 1\n\t} else if len(v.Pre) > 0 && len(o.Pre) == 0 {\n\t\treturn -1\n\t} else {\n\n\t\ti := 0\n\t\tfor ; i < len(v.Pre) && i < len(o.Pre); i++ {\n\t\t\tif comp := v.Pre[i].Compare(o.Pre[i]); comp == 0 {\n\t\t\t\tcontinue\n\t\t\t} else if comp == 1 {\n\t\t\t\treturn 1\n\t\t\t} else {\n\t\t\t\treturn -1\n\t\t\t}\n\t\t}\n\n\t\t// If all pr versions are the equal but one has further pr version, this one greater\n\t\tif i == len(v.Pre) && i == len(o.Pre) {\n\t\t\treturn 0\n\t\t} else if i == len(v.Pre) && i < len(o.Pre) {\n\t\t\treturn -1\n\t\t} else {\n\t\t\treturn 1\n\t\t}\n\n\t}\n}\n\n// Validates v and returns error in case\nfunc (v *Version) Validate() error {\n\t// Major, Minor, Patch already validated using uint64\n\n\tif len(v.Pre) > 0 {\n\t\tfor _, pre := range v.Pre {\n\t\t\tif !pre.IsNum { //Numeric prerelease versions already uint64\n\t\t\t\tif len(pre.VersionStr) == 0 {\n\t\t\t\t\treturn fmt.Errorf(\"Prerelease can not be empty %q\", pre.VersionStr)\n\t\t\t\t}\n\t\t\t\tif !containsOnly(pre.VersionStr, alphanum) {\n\t\t\t\t\treturn fmt.Errorf(\"Invalid character(s) found in prerelease %q\", pre.VersionStr)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(v.Build) > 0 {\n\t\tfor _, build := range v.Build {\n\t\t\tif len(build) == 0 {\n\t\t\t\treturn fmt.Errorf(\"Build meta data can not be empty %q\", build)\n\t\t\t}\n\t\t\tif !containsOnly(build, alphanum) {\n\t\t\t\treturn fmt.Errorf(\"Invalid character(s) found in build meta data %q\", build)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Alias for Parse, parses version string and returns a validated Version or error\nfunc New(s string) (*Version, error) {\n\treturn Parse(s)\n}\n\n// Parses version string and returns a validated Version or error\nfunc Parse(s string) (*Version, error) {\n\ts = strings.Replace(s, \"v\", \"\", 1)\n\tif len(s) == 0 {\n\t\treturn nil, errors.New(\"Version string empty\")\n\t}\n\n\t// Split into major.minor.(patch+pr+meta)\n\tparts := strings.SplitN(s, \".\", 3)\n\tif len(parts) != 3 {\n\t\treturn nil, errors.New(\"No Major.Minor.Patch elements found\")\n\t}\n\n\t// Major\n\tif !containsOnly(parts[0], numbers) {\n\t\treturn nil, fmt.Errorf(\"Invalid character(s) found in major number %q\", parts[0])\n\t}\n\tif hasLeadingZeroes(parts[0]) {\n\t\treturn nil, fmt.Errorf(\"Major number must not contain leading zeroes %q\", parts[0])\n\t}\n\tmajor, err := strconv.ParseUint(parts[0], 10, 64)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Minor\n\tif !containsOnly(parts[1], numbers) {\n\t\treturn nil, fmt.Errorf(\"Invalid character(s) found in minor number %q\", parts[1])\n\t}\n\tif hasLeadingZeroes(parts[1]) {\n\t\treturn nil, fmt.Errorf(\"Minor number must not contain leading zeroes %q\", parts[1])\n\t}\n\tminor, err := strconv.ParseUint(parts[1], 10, 64)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpreIndex := strings.Index(parts[2], \"-\")\n\tbuildIndex := strings.Index(parts[2], \"+\")\n\n\t// Determine last index of patch version (first of pre or build versions)\n\tvar subVersionIndex int\n\tif preIndex != -1 && buildIndex == -1 {\n\t\tsubVersionIndex = preIndex\n\t} else if preIndex == -1 && buildIndex != -1 {\n\t\tsubVersionIndex = buildIndex\n\t} else if preIndex == -1 && buildIndex == -1 {\n\t\tsubVersionIndex = len(parts[2])\n\t} else {\n\t\t// if there is no actual pr version but a hyphen inside the build meta data\n\t\tif buildIndex < preIndex {\n\t\t\tsubVersionIndex = buildIndex\n\t\t\tpreIndex = -1 // Build meta data before preIndex found implicates there are no prerelease versions\n\t\t} else {\n\t\t\tsubVersionIndex = preIndex\n\t\t}\n\t}\n\n\tif !containsOnly(parts[2][:subVersionIndex], numbers) {\n\t\treturn nil, fmt.Errorf(\"Invalid character(s) found in patch number %q\", parts[2][:subVersionIndex])\n\t}\n\tif hasLeadingZeroes(parts[2][:subVersionIndex]) {\n\t\treturn nil, fmt.Errorf(\"Patch number must not contain leading zeroes %q\", parts[2][:subVersionIndex])\n\t}\n\tpatch, err := strconv.ParseUint(parts[2][:subVersionIndex], 10, 64)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tv := &Version{}\n\tv.Major = major\n\tv.Minor = minor\n\tv.Patch = patch\n\n\t// There are PreRelease versions\n\tif preIndex != -1 {\n\t\tvar preRels string\n\t\tif buildIndex != -1 {\n\t\t\tpreRels = parts[2][subVersionIndex+1 : buildIndex]\n\t\t} else {\n\t\t\tpreRels = parts[2][subVersionIndex+1:]\n\t\t}\n\t\tprparts := strings.Split(preRels, \".\")\n\t\tfor _, prstr := range prparts {\n\t\t\tparsedPR, err := NewPRVersion(prstr)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tv.Pre = append(v.Pre, parsedPR)\n\t\t}\n\t}\n\n\t// There is build meta data\n\tif buildIndex != -1 {\n\t\tbuildStr := parts[2][buildIndex+1:]\n\t\tbuildParts := strings.Split(buildStr, \".\")\n\t\tfor _, str := range buildParts {\n\t\t\tif len(str) == 0 {\n\t\t\t\treturn nil, errors.New(\"Build meta data is empty\")\n\t\t\t}\n\t\t\tif !containsOnly(str, alphanum) {\n\t\t\t\treturn nil, fmt.Errorf(\"Invalid character(s) found in build meta data %q\", str)\n\t\t\t}\n\t\t\tv.Build = append(v.Build, str)\n\t\t}\n\t}\n\n\treturn v, nil\n}\n\n// PreRelease Version\ntype PRVersion struct {\n\tVersionStr string\n\tVersionNum uint64\n\tIsNum      bool\n}\n\n// Creates a new valid prerelease version\nfunc NewPRVersion(s string) (*PRVersion, error) {\n\tif len(s) == 0 {\n\t\treturn nil, errors.New(\"Prerelease is empty\")\n\t}\n\tv := &PRVersion{}\n\tif containsOnly(s, numbers) {\n\t\tif hasLeadingZeroes(s) {\n\t\t\treturn nil, fmt.Errorf(\"Numeric PreRelease version must not contain leading zeroes %q\", s)\n\t\t}\n\t\tnum, err := strconv.ParseUint(s, 10, 64)\n\n\t\t// Might never be hit, but just in case\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tv.VersionNum = num\n\t\tv.IsNum = true\n\t} else if containsOnly(s, alphanum) {\n\t\tv.VersionStr = s\n\t\tv.IsNum = false\n\t} else {\n\t\treturn nil, fmt.Errorf(\"Invalid character(s) found in prerelease %q\", s)\n\t}\n\treturn v, nil\n}\n\n// Is pre release version numeric?\nfunc (v *PRVersion) IsNumeric() bool {\n\treturn v.IsNum\n}\n\n// Compares PreRelease Versions v to o:\n// -1 == v is less than o\n// 0 == v is equal to o\n// 1 == v is greater than o\nfunc (v *PRVersion) Compare(o *PRVersion) int {\n\tif v.IsNum && !o.IsNum {\n\t\treturn -1\n\t} else if !v.IsNum && o.IsNum {\n\t\treturn 1\n\t} else if v.IsNum && o.IsNum {\n\t\tif v.VersionNum == o.VersionNum {\n\t\t\treturn 0\n\t\t} else if v.VersionNum > o.VersionNum {\n\t\t\treturn 1\n\t\t} else {\n\t\t\treturn -1\n\t\t}\n\t} else { // both are Alphas\n\t\tif v.VersionStr == o.VersionStr {\n\t\t\treturn 0\n\t\t} else if v.VersionStr > o.VersionStr {\n\t\t\treturn 1\n\t\t} else {\n\t\t\treturn -1\n\t\t}\n\t}\n}\n\n// PreRelease version to string\nfunc (v *PRVersion) String() string {\n\tif v.IsNum {\n\t\treturn strconv.FormatUint(v.VersionNum, 10)\n\t}\n\treturn v.VersionStr\n}\n\nfunc containsOnly(s string, set string) bool {\n\treturn strings.IndexFunc(s, func(r rune) bool {\n\t\treturn !strings.ContainsRune(set, r)\n\t}) == -1\n}\n\nfunc hasLeadingZeroes(s string) bool {\n\treturn len(s) > 1 && s[0] == '0'\n}\n\n// Creates a new valid build version\nfunc NewBuildVersion(s string) (string, error) {\n\tif len(s) == 0 {\n\t\treturn \"\", errors.New(\"Buildversion is empty\")\n\t}\n\tif !containsOnly(s, alphanum) {\n\t\treturn \"\", fmt.Errorf(\"Invalid character(s) found in build meta data %q\", s)\n\t}\n\treturn s, nil\n}\n"
  },
  {
    "path": "src/upgrade/check.go",
    "content": "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\"\n\t\"time\"\n\n\t\"github.com/dustin/go-humanize\"\n\t\"github.com/go-toast/toast\"\n)\n\nfunc Check(root string, nvmversion string) {\n\t// Store the recognized version to prevent duplicates\n\tnotices := LoadNotices()\n\tdefer notices.Save()\n\n\treg := LoadRegistration(os.Args[2:]...)\n\n\t// Check for Node.js updates\n\tif reg.LTS || reg.Current {\n\t\tbuf, err := get(web.GetFullNodeUrl(\"index.json\"))\n\t\tabortOnError(err)\n\n\t\tvar data = make([]map[string]interface{}, 0)\n\t\tabortOnError(json.Unmarshal([]byte(buf), &data))\n\n\t\tlastLTS := notices.LastLTS()\n\t\tlastCurrent := notices.LastCurrent()\n\t\tinstalled := node.GetInstalled(root)\n\t\tchanges := 0\n\t\tfor _, value := range data {\n\t\t\tswitch value[\"lts\"].(type) {\n\t\t\t// LTS versions always have the lts attribute as a string\n\t\t\tcase string:\n\t\t\t\tif reg.LTS {\n\t\t\t\t\tversionDate, _ := time.Parse(\"2006-01-02\", value[\"date\"].(string))\n\t\t\t\t\tif versionDate.After(lastLTS) && versionDate.After(time.Now().AddDate(0, -1, 0)) && !in(strings.Replace(value[\"version\"].(string), \"v\", \"\", 1), installed) {\n\t\t\t\t\t\tabortOnError(alertRelease(value))\n\t\t\t\t\t\tchanges++\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t// Current versions always have the lts attribute as a false boolean\n\t\t\tcase bool:\n\t\t\t\tif reg.Current && !value[\"lts\"].(bool) {\n\t\t\t\t\tversionDate, _ := time.Parse(\"2006-01-02\", value[\"date\"].(string))\n\t\t\t\t\tif versionDate.After(lastCurrent) && versionDate.After(time.Now().AddDate(0, -1, 0)) && !in(strings.Replace(value[\"version\"].(string), \"v\", \"\", 1), installed) {\n\t\t\t\t\t\tabortOnError(alertRelease(value))\n\t\t\t\t\t\tchanges++\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif changes == 0 {\n\t\t\tnoupdate()\n\t\t\treturn\n\t\t}\n\t}\n\n\t// Check for NVM for Windows updates\n\tif reg.NVM4W {\n\t\tbuf, err := get(\"https://api.github.com/repos/coreybutler/nvm-windows/releases/latest\")\n\t\tabortOnError(err)\n\n\t\tvar data map[string]interface{}\n\t\tabortOnError(json.Unmarshal([]byte(buf), &data))\n\n\t\tcurrent, err := semver.New(nvmversion)\n\t\tabortOnError(err)\n\t\tnext, err := semver.New(data[\"tag_name\"].(string))\n\t\tabortOnError(err)\n\n\t\tnotices.NVM4W = current.String()\n\t\tif !next.GT(current) {\n\t\t\talertNvmRelease(current, next, data)\n\t\t\tnotices.NVM4W = next.String()\n\t\t}\n\t}\n\n\tnow := time.Now().Format(\"2006-01-02\")\n\tif reg.LTS {\n\t\tnotices.LTS = now\n\t}\n\tif reg.Current {\n\t\tnotices.Current = now\n\t}\n}\n\nfunc alertNvmRelease(current, next *semver.Version, data map[string]interface{}) {\n\texe, _ := os.Executable()\n\tpath := filepath.Dir(exe)\n\ticonPath := filepath.Join(path, \"nodejs.ico\")\n\tpubDate, _ := time.Parse(\"2006-01-02T15:04:05Z\", data[\"published_at\"].(string))\n\tage := humanize.Time(pubDate)\n\n\tnotification := toast.Notification{\n\t\tAppID:   \"NVM for Windows\",\n\t\tTitle:   \"NVM for Windows Update Available\",\n\t\tMessage: fmt.Sprintf(\"Version %s is was released %s.\\n(currently using v%s)\", next.String(), age, current.String()),\n\t\tIcon:    iconPath,\n\t\tActions: []toast.Action{\n\t\t\t{\"protocol\", \"Install\", \"nvm://launch?action=upgrade\"},\n\t\t\t{\"protocol\", \"View\", data[\"html_url\"].(string)},\n\t\t},\n\t}\n\n\t// Display the notification\n\terr := notification.Push()\n\tif err != nil {\n\t\tabortOnError(err)\n\t}\n}\n\nfunc in(item string, set []string) bool {\n\tfor _, i := range set {\n\t\tif i == item {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc noupdate() {\n\tfmt.Println(\"no new releases detected\")\n}\n\nfunc UpgradeCompleteAlert(version string) {\n\texe, _ := os.Executable()\n\tpath := filepath.Dir(exe)\n\ticonPath := filepath.Join(path, \"checkmark.ico\")\n\n\tnotification := toast.Notification{\n\t\tAppID:   \"NVM for Windows\",\n\t\tTitle:   \"Upgrade Complete\",\n\t\tMessage: fmt.Sprintf(\"The upgrade to NVM for Windows v%s completed successfully.\", version),\n\t\tIcon:    iconPath,\n\t\tActions: []toast.Action{\n\t\t\t{\"protocol\", \"Open PowerShell\", \"nvm://launch?action=open_terminal&amp;type=pwsh\"},\n\t\t\t{\"protocol\", \"Open CMD Prompt\", \"nvm://launch?action=open_terminal&amp;type=cmd\"},\n\t\t},\n\t}\n\n\t// Display the notification\n\terr := notification.Push()\n\tif err != nil {\n\t\tabortOnError(err)\n\t}\n}\n\nfunc alertRelease(data map[string]interface{}) error {\n\tversion, err := semver.New(data[\"version\"].(string))\n\tif err != nil {\n\t\treturn err\n\t}\n\texe, _ := os.Executable()\n\tpath := filepath.Dir(exe)\n\ticonPath := filepath.Join(path, \"nodejs.ico\")\n\treleaseName := \"\"\n\treleaseDate, err := time.Parse(\"2006-01-02\", data[\"date\"].(string))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tage := humanize.Time(releaseDate)\n\tmsg := fmt.Sprintf(\"with npm v%s & V8 v%s\\nReleased %s.\", data[\"npm\"].(string), data[\"v8\"].(string), age)\n\n\tswitch data[\"lts\"].(type) {\n\tcase string:\n\t\treleaseName = \" (LTS \" + data[\"lts\"].(string) + \")\"\n\t}\n\n\tif data[\"security\"].(bool) {\n\t\tmsg += \"\\nThis is a security release.\"\n\t}\n\n\ttitle := fmt.Sprintf(\"Node.js v%s Available%s\", version.String(), releaseName)\n\n\tnotification := toast.Notification{\n\t\tAppID:   \"NVM for Windows\",\n\t\tTitle:   title,\n\t\tMessage: msg,\n\t\tIcon:    iconPath,\n\t}\n\n\t// Display the notification\n\terr = notification.Push()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "src/upgrade/notification.go",
    "content": "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 string\n\tLTS     string `json:\"lts,omitempty\"`\n\tCurrent string `json:\"current,omitempty\"`\n\tNVM4W   string `json:\"nvm4w,omitempty\"`\n\tAuthor  string `json:\"author,omitempty\"`\n}\n\nfunc LoadNotices() *LastNotification {\n\tln := &LastNotification{}\n\tnoticedata, err := os.ReadFile(ln.File())\n\tif err != nil {\n\t\tif !os.IsNotExist(err) {\n\t\t\tabortOnError(err)\n\t\t}\n\t}\n\n\tif noticedata != nil {\n\t\tabortOnError(json.Unmarshal(noticedata, &ln))\n\t}\n\n\treturn ln\n}\n\nfunc (ln *LastNotification) Path() string {\n\tif ln.outpath == \"\" {\n\t\tln.outpath = filepath.Join(os.Getenv(\"APPDATA\"), \".nvm\")\n\t}\n\treturn ln.outpath\n}\n\nfunc (ln *LastNotification) File() string {\n\treturn filepath.Join(ln.Path(), \".updates.json\")\n}\n\nfunc (ln *LastNotification) Save() {\n\toutput, err := json.Marshal(ln)\n\tabortOnError(err)\n\tabortOnError(os.MkdirAll(ln.Path(), os.ModePerm))\n\tabortOnError(os.WriteFile(ln.File(), output, os.ModePerm))\n\tabortOnError(setHidden(ln.Path()))\n}\n\nfunc (ln *LastNotification) LastLTS() time.Time {\n\tif ln.LTS == \"\" {\n\t\treturn time.Now()\n\t}\n\n\tt, _ := time.Parse(\"2006-01-02\", ln.LTS)\n\treturn t\n}\n\nfunc (ln *LastNotification) LastCurrent() time.Time {\n\tif ln.Current == \"\" {\n\t\treturn time.Now()\n\t}\n\n\tt, _ := time.Parse(\"2006-01-02\", ln.Current)\n\treturn t\n}\n"
  },
  {
    "path": "src/upgrade/register.go",
    "content": "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     = \"NVM for Windows Node.js LTS Update Check\"\n\tNODE_CURRENT_SCHEDULE_NAME = \"NVM for Windows Node.js Current Update Check\"\n\tNVM4W_SCHEDULE_NAME        = \"NVM for Windows Update Check\"\n\tAUTHOR_SCHEDULE_NAME       = \"NVM for Windows Author Update Check\"\n)\n\ntype Registration struct {\n\tLTS     bool\n\tCurrent bool\n\tNVM4W   bool\n\tAuthor  bool\n}\n\nfunc LoadRegistration(args ...string) *Registration {\n\treg := &Registration{\n\t\tLTS:     false,\n\t\tCurrent: false,\n\t\tNVM4W:   false,\n\t\tAuthor:  false,\n\t}\n\n\tfor _, arg := range args {\n\t\targ = strings.ToLower(strings.ReplaceAll(arg, \"--\", \"\"))\n\t\tswitch arg {\n\t\tcase \"lts\":\n\t\t\treg.LTS = true\n\t\tcase \"current\":\n\t\t\treg.Current = true\n\t\tcase \"nvm4w\":\n\t\t\treg.NVM4W = true\n\t\tcase \"author\":\n\t\t\treg.Author = true\n\t\t}\n\t}\n\n\treturn reg\n}\n\nfunc abortOnError(err error) {\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\tos.WriteFile(\"./error.log\", []byte(err.Error()), os.ModePerm)\n\t\tos.Exit(1)\n\t}\n}\n\nfunc logError(err error) {\n\tfmt.Println(err)\n\tif err != nil {\n\t\tos.WriteFile(\"./error.log\", []byte(err.Error()), os.ModePerm)\n\t}\n}\n\nfunc Register() {\n\treg := LoadRegistration(os.Args[2:]...)\n\texe, _ := os.Executable()\n\n\tif reg.LTS {\n\t\tabortOnError(ScheduleTask(NODE_LTS_SCHEDULE_NAME, fmt.Sprintf(`\"%s\" checkForUpdates lts`, exe), \"HOURLY\", \"00:30\"))\n\t}\n\tif reg.Current {\n\t\tabortOnError(ScheduleTask(NODE_CURRENT_SCHEDULE_NAME, fmt.Sprintf(`\"%s\" checkForUpdates current`, exe), \"HOURLY\", \"00:25\"))\n\t}\n\tif reg.NVM4W {\n\t\tabortOnError(ScheduleTask(NVM4W_SCHEDULE_NAME, fmt.Sprintf(`\"%s\" checkForUpdates nvm4w`, exe), \"HOURLY\", \"00:15\"))\n\t}\n\tif reg.Author {\n\t\tabortOnError(ScheduleTask(AUTHOR_SCHEDULE_NAME, fmt.Sprintf(`\"%s\" checkForUpdates author`, exe), \"HOURLY\", \"00:45\"))\n\t}\n}\n\nfunc Unregister() {\n\treg := LoadRegistration(os.Args[2:]...)\n\n\tif reg.LTS {\n\t\tabortOnError(UnscheduleTask(NODE_LTS_SCHEDULE_NAME))\n\t}\n\tif reg.Current {\n\t\tabortOnError(UnscheduleTask(NODE_CURRENT_SCHEDULE_NAME))\n\t}\n\tif reg.NVM4W {\n\t\tabortOnError(UnscheduleTask(NVM4W_SCHEDULE_NAME))\n\t}\n\tif reg.Author {\n\t\tabortOnError(UnscheduleTask(AUTHOR_SCHEDULE_NAME))\n\t}\n}\n\n// interval can be:\n// MINUTE\tRuns the task every N minutes. Requires /MO (modifier) to specify the interval.\n// HOURLY\tRuns the task every N hours. Requires /MO to specify the interval.\n// DAILY\tRuns the task every N days. Requires /MO to specify the interval.\n// WEEKLY\tRuns the task every N weeks. Requires /MO and /D (days of the week) to specify the schedule.\n// MONTHLY\tRuns the task on specific days of the month. Requires /MO, /D, or /M for further specifics.\n// ONCE\tRuns the task only once at the specified time. Requires /ST (start time).\n// ONSTART\tRuns the task every time the system starts.\n// ONLOGON\tRuns the task every time the user logs on.\n// ONIDLE\tRuns the task when the system is idle for a specified amount of time.\n// EVENT\tRuns the task when a specific event is triggered. Requires /EC (event log) and /ID (event ID).\nfunc ScheduleTask(name string, command string, interval string, startTime ...string) error {\n\tswitch strings.ToUpper(interval) {\n\tcase \"MINUTE\":\n\t\tfallthrough\n\tcase \"HOURLY\":\n\t\tfallthrough\n\tcase \"DAILY\":\n\t\tfallthrough\n\tcase \"WEEKLY\":\n\t\tfallthrough\n\tcase \"MONTHLY\":\n\t\tfallthrough\n\tcase \"ONCE\":\n\t\tfallthrough\n\tcase \"ONSTART\":\n\t\tfallthrough\n\tcase \"ONLOGON\":\n\t\tfallthrough\n\tcase \"ONIDLE\":\n\t\tfallthrough\n\tcase \"EVENT\":\n\t\tinterval = strings.ToUpper(interval)\n\tdefault:\n\t\treturn fmt.Errorf(\"scheduling error: invalid interval %q\", interval)\n\t}\n\n\tstart := \"00:00\"\n\tif len(startTime) > 0 {\n\t\tstart = startTime[0]\n\t}\n\n\ttmp, err := os.MkdirTemp(\"\", \"nvm4w-regitration-*\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"scheduling error: %v\", err)\n\t}\n\tdefer os.RemoveAll(tmp)\n\n\tscript := fmt.Sprintf(`\n@echo off\nset errorlog=\"error.log\"\nset output=\"%s\\output.log\"\nschtasks /create /tn \"%s\" /tr \"cmd.exe /c %s\" /sc %s /st %s /F > %%output%% 2>&1\nif not errorlevel 0 (\n\techo ERROR: Failed to create scheduled task: exit code: %%errorlevel%% >> %%errorlog%%\n\ttype %%output%% >> %%errorlog%%\n\texit /b %%errorlevel%%\n)\n\t`, tmp, name, escapeBackslashes(command), strings.ToLower(interval), start)\n\n\terr = os.WriteFile(filepath.Join(tmp, \"schedule.bat\"), []byte(script), os.ModePerm)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"scheduling error: %v\", err)\n\t}\n\n\tcmd := exec.Command(filepath.Join(tmp, \"schedule.bat\"))\n\n\t// Capture standard output and standard error\n\tout, err := cmd.CombinedOutput()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"scheduling error: %v\\n%s\", err, out)\n\t}\n\n\t// fmt.Sprintf(`\"%s\" task scheduled successfully!`, name)\n\n\treturn nil\n}\n\nfunc UnscheduleTask(name string) error {\n\ttmp, err := os.MkdirTemp(\"\", \"nvm4w-registration-*\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"scheduling error: %v\", err)\n\t}\n\tdefer os.RemoveAll(tmp)\n\n\tscript := fmt.Sprintf(`\n@echo off\nset errorlog=\"error.log\"\nset output=\"%s\\output.log\"\nschtasks /delete /tn \"%s\" /f > %%output%% 2>&1\nif not errorlevel 0 (\n\techo failed to remove scheduled task: exit code: %%errorlevel%% >> %%errorlog%%\n\ttype %%output%% >> %%errorlog%%\n\texit /b %%errorlevel%%\n)\n\t`, tmp, name)\n\n\terr = os.WriteFile(filepath.Join(tmp, \"unschedule.bat\"), []byte(script), os.ModePerm)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unscheduling error: %v\", err)\n\t}\n\n\tcmd := exec.Command(filepath.Join(tmp, \"unschedule.bat\"))\n\n\t// Capture standard output and standard error\n\tout, err := cmd.CombinedOutput()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unscheduling error: %v\\n%s\", err, out)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "src/upgrade/upgrade.go",
    "content": "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/semver\"\n\t\"nvm/utility\"\n\t\"os\"\n\t\"os/exec\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\t\"syscall\"\n\t\"time\"\n\t\"unsafe\"\n\n\t\"github.com/coreybutler/go-fsutil\"\n\t\"github.com/ncruces/zenity\"\n\t\"golang.org/x/sys/windows\"\n)\n\nconst (\n\tUPDATE_URL = \"https://api.github.com/repos/coreybutler/nvm-windows/releases/latest\"\n\tALERTS_URL = \"https://author.io/nvm4w/feed/alerts\"\n\t// Color codes\n\tyellow = \"\\033[33m\"\n\treset  = \"\\033[0m\"\n\n\t// Windows console modes\n\tENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004\n\tFILE_ATTRIBUTE_HIDDEN              = 0x2\n\tCREATE_NEW_CONSOLE                 = 0x00000010 // Create a new console for the child process\n\tDETACHED_PROCESS                   = 0x00000008 // Detach the child process from the parent\n\n\twarningIcon = \"⚠️\"\n\t// exclamationIcon = \"❗\"\n)\n\ntype Notification struct {\n\tAppID    string   `json:\"app_id\"`\n\tTitle    string   `json:\"title\"`\n\tMessage  string   `json:\"message\"`\n\tIcon     string   `json:\"icon\"`\n\tActions  []Action `json:\"actions\"`\n\tDuration string   `json:\"duration\"`\n\tLink     string   `json:\"link\"`\n}\n\ntype Action struct {\n\tType  string `json:\"type\"`\n\tLabel string `json:\"label\"`\n\tURI   string `json:\"uri\"`\n}\n\nfunc display(data Notification) {\n\tdata.AppID = \"NVM for Windows\"\n\tcontent, _ := json.Marshal(data)\n\tgo author.Bridge(\"notify\", string(content))\n}\n\ntype Update struct {\n\tVersion         string   `json:\"version\"`\n\tAssets          []string `json:\"assets\"`\n\tWarnings        []string `json:\"notices\"`\n\tVersionWarnings []string `json:\"versionNotices\"`\n\tSourceURL       string   `json:\"sourceTpl\"`\n}\n\ntype Release struct {\n\tVersion string                   `json:\"name\"`\n\tAssets  []map[string]interface{} `json:\"assets\"`\n\tPublish time.Time                `json:\"published_at\"`\n}\n\nfunc Run(version string) error {\n\tshow_progress := false\n\tfor _, arg := range os.Args[2:] {\n\t\tif strings.ToLower(arg) == \"--show-progress-ui\" {\n\t\t\tshow_progress = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\tstatus := make(chan Status)\n\n\tif !show_progress {\n\t\t// Setup signal handling first\n\t\tsignalChan := make(chan os.Signal, 1)\n\t\tsignal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)\n\t\tdefer signal.Stop(signalChan)\n\n\t\t// Add signal handler\n\t\tgo func() {\n\t\t\t<-signalChan\n\t\t\tfmt.Println(\"Installation canceled by user\")\n\t\t\tos.Exit(0)\n\t\t}()\n\n\t\tgo func() {\n\t\t\tfor {\n\t\t\t\tselect {\n\t\t\t\tcase s := <-status:\n\t\t\t\t\tif s.Warn != \"\" {\n\t\t\t\t\t\tWarn(s.Warn)\n\t\t\t\t\t}\n\t\t\t\t\tif s.Err != nil {\n\t\t\t\t\t\tfmt.Println(s.Err)\n\t\t\t\t\t\tos.Exit(1)\n\t\t\t\t\t}\n\n\t\t\t\t\tif s.Text != \"\" {\n\t\t\t\t\t\tfmt.Println(s.Text)\n\t\t\t\t\t}\n\n\t\t\t\t\tif s.Done {\n\t\t\t\t\t\tfmt.Println(\"Upgrade complete\")\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\n\t\ttime.Sleep(300 * time.Millisecond)\n\n\t\treturn run(version, status)\n\t}\n\n\twg := &sync.WaitGroup{}\n\twg.Add(1)\n\n\tvar dlg zenity.ProgressDialog\n\tvar exitCode = 0\n\tvar u *Update\n\n\t// Display visual progress UI\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase s := <-status:\n\t\t\t\tif s.Warn != \"\" {\n\t\t\t\t\tdisplay(Notification{\n\t\t\t\t\t\tMessage: s.Warn,\n\t\t\t\t\t\tIcon:    \"nvm\",\n\t\t\t\t\t})\n\t\t\t\t}\n\n\t\t\t\tif s.Cancel {\n\t\t\t\t\tdisplay(Notification{\n\t\t\t\t\t\tTitle:   \"Installation Canceled\",\n\t\t\t\t\t\tMessage: fmt.Sprintf(\"Installation of NVM for Windows v%s was canceled by the user.\", u.Version),\n\t\t\t\t\t\tIcon:    \"error\",\n\t\t\t\t\t\tActions: []Action{\n\t\t\t\t\t\t\t{Type: \"protocol\", Label: \"Install Again\", URI: fmt.Sprintf(\"nvm://launch?action=upgrade&version=%s\", u.Version)},\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif s.Err != nil {\n\t\t\t\t\texitCode = 1\n\t\t\t\t\tdisplay(Notification{\n\t\t\t\t\t\tTitle:   \"Installation Error\",\n\t\t\t\t\t\tMessage: s.Err.Error(),\n\t\t\t\t\t\tIcon:    \"error\",\n\t\t\t\t\t})\n\n\t\t\t\t\tdlg.Text(fmt.Sprintf(\"error: %v\", s.Err))\n\t\t\t\t\tdlg.Close()\n\n\t\t\t\t\t// err := restore(status, verbose)\n\t\t\t\t\t// if err != nil {\n\t\t\t\t\t// \tif show_progress {\n\t\t\t\t\t// \t\tdisplay(notify.Notification{\n\t\t\t\t\t// \t\t\tTitle:   \"Rollback Error\",\n\t\t\t\t\t// \t\t\tMessage: err.Error(),\n\t\t\t\t\t// \t\t\tIcon:    \"error\",\n\t\t\t\t\t// \t\t})\n\t\t\t\t\t// \t} else {\n\t\t\t\t\t// \t\tfmt.Printf(\"rollback error: %v\\n\", err)\n\t\t\t\t\t// \t}\n\t\t\t\t\t// }\n\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif s.Done {\n\t\t\t\t\tdisplay(Notification{\n\t\t\t\t\t\tTitle:   \"Upgrade Complete\",\n\t\t\t\t\t\tMessage: fmt.Sprintf(\"Now running version %s.\", u.Version),\n\t\t\t\t\t\tIcon:    \"success\",\n\t\t\t\t\t})\n\n\t\t\t\t\tdlg.Text(\"Upgrade complete\")\n\t\t\t\t\ttime.Sleep(1 * time.Second)\n\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tvar empty string\n\t\t\t\tif s.Text != empty && len(strings.TrimSpace(s.Text)) > 0 {\n\t\t\t\t\tdlg.Text(s.Text)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}()\n\n\t// Wait for the prior subroutine to initialize before starting the next dependent thread\n\ttime.Sleep(300 * time.Millisecond)\n\n\t// Run the update\n\tgo func() {\n\t\texe, _ := os.Executable()\n\t\twinIco := filepath.Join(filepath.Dir(exe), \"nvm.ico\")\n\t\tico := filepath.Join(filepath.Dir(exe), \"download.ico\")\n\n\t\tvar err error\n\t\tu, err = checkForUpdate(UPDATE_URL)\n\t\tif err != nil {\n\t\t\tdisplay(Notification{\n\t\t\t\tTitle:   \"Update Error\",\n\t\t\t\tMessage: err.Error(),\n\t\t\t\tIcon:    \"error\",\n\t\t\t})\n\n\t\t\tstatus <- Status{Err: fmt.Errorf(\"error: failed to obtain update data: %v\\n\", err)}\n\t\t}\n\n\t\tvar perr error\n\t\tdlg, perr = zenity.Progress(\n\t\t\tzenity.Title(fmt.Sprintf(\"Installing NVM for Windows v%s\", u.Version)),\n\t\t\tzenity.Icon(ico),\n\t\t\tzenity.WindowIcon(winIco),\n\t\t\tzenity.AutoClose(),\n\t\t\tzenity.NoCancel(),\n\t\t\tzenity.Pulsate())\n\n\t\tif perr != nil {\n\t\t\tfmt.Println(\"Failed to create progress dialog\")\n\t\t}\n\n\t\tgo func() {\n\t\t\tfor {\n\t\t\t\tselect {\n\t\t\t\tcase <-dlg.Done():\n\t\t\t\t\tif err := dlg.Complete(); err == zenity.ErrCanceled {\n\t\t\t\t\t\tstatus <- Status{Cancel: true}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t\tstatus <- Status{Text: \"Validating version...\"}\n\n\t\trun(version, status, u)\n\t}()\n\n\twg.Wait()\n\tos.Exit(exitCode)\n\n\treturn nil\n}\n\nfunc run(version string, status chan Status, updateMetadata ...*Update) error {\n\targs := os.Args[2:]\n\tcolorize := true\n\tif err := EnableVirtualTerminalProcessing(); err != nil {\n\t\tcolorize = false\n\t}\n\n\t// Retrieve remote metadata\n\tvar update *Update\n\tif len(updateMetadata) > 0 {\n\t\tupdate = updateMetadata[0]\n\t} else {\n\t\tvar err error\n\t\tupdate, err = checkForUpdate(UPDATE_URL)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error: failed to obtain update data: %v\\n\", err)\n\t\t}\n\t}\n\n\tfor _, warning := range update.Warnings {\n\t\tstatus <- Status{Warn: warning}\n\t}\n\n\tverbose := false\n\t// rollback := false\n\tfor _, arg := range args {\n\t\tswitch strings.ToLower(arg) {\n\t\tcase \"--verbose\":\n\t\t\tverbose = true\n\t\t\t// case \"rollback\":\n\t\t\t// \trollback = true\n\t\t}\n\t}\n\n\t// // Check for a backup\n\t// if rollback {\n\t// \tif fsutil.Exists(filepath.Join(\".\", \".update\", \"nvm4w-backup.zip\")) {\n\t// \t\tfmt.Println(\"restoring NVM4W backup...\")\n\t// \t\trbtmp, err := os.MkdirTemp(\"\", \"nvm-rollback-*\")\n\t// \t\tif err != nil {\n\t// \t\t\tfmt.Printf(\"error: failed to create rollback directory: %v\\n\", err)\n\t// \t\t\tos.Exit(1)\n\t// \t\t}\n\t// \t\tdefer os.RemoveAll(rbtmp)\n\n\t// \t\terr = unzip(filepath.Join(\".\", \".update\", \"nvm4w-backup.zip\"), rbtmp)\n\t// \t\tif err != nil {\n\t// \t\t\tfmt.Printf(\"error: failed to extract backup: %v\\n\", err)\n\t// \t\t\tos.Exit(1)\n\t// \t\t}\n\n\t// \t\t// Copy the backup files to the current directory\n\t// \t\terr = copyDirContents(rbtmp, \".\")\n\t// \t\tif err != nil {\n\t// \t\t\tfmt.Printf(\"error: failed to restore backup files: %v\\n\", err)\n\t// \t\t\tos.Exit(1)\n\t// \t\t}\n\n\t// \t\t// Remove the restoration directory\n\t// \t\tos.RemoveAll(filepath.Join(\".\", \".update\"))\n\n\t// \t\tfmt.Println(\"rollback complete\")\n\t// \t\trbcmd := exec.Command(\"nvm.exe\", \"version\")\n\t// \t\to, err := rbcmd.Output()\n\t// \t\tif err != nil {\n\t// \t\t\tfmt.Println(\"error running nvm.exe:\", err)\n\t// \t\t\tos.Exit(1)\n\t// \t\t}\n\n\t// \t\texec.Command(\"schtasks\", \"/delete\", \"/tn\", \"\\\"RemoveNVM4WBackup\\\"\", \"/f\").Run()\n\t// \t\tfmt.Printf(\"rollback to v%s complete\\n\", string(o))\n\t// \t\tos.Exit(0)\n\t// \t} else {\n\t// \t\tfmt.Println(\"no backup available: backups are only available for 7 days after upgrading\")\n\t// \t\tos.Exit(0)\n\t// \t}\n\t// }\n\n\tcurrentVersion, err := semver.New(version)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tupdateVersion, err := semver.New(update.Version)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif currentVersion.LT(updateVersion) {\n\t\tif len(update.VersionWarnings) > 0 {\n\t\t\tif len(update.Warnings) > 0 || len(update.VersionWarnings) > 0 {\n\t\t\t\tfmt.Println(\"\")\n\t\t\t}\n\t\t\tfor _, warning := range update.VersionWarnings {\n\t\t\t\tstatus <- Status{Warn: warning}\n\t\t\t\tWarn(warning, colorize)\n\t\t\t}\n\t\t\tfmt.Println(\"\")\n\t\t}\n\t\tfmt.Printf(\"upgrading from v%s-->%s\\n\", version, highlight(update.Version))\n\t\tstatus <- Status{Text: \"downloading...\"}\n\t} else {\n\t\tstatus <- Status{Text: \"nvm is up to date\", Done: true}\n\t\treturn nil\n\t}\n\n\t// Make temp directory\n\ttmp, err := os.MkdirTemp(\"\", \"nvm-upgrade-*\")\n\tif err != nil {\n\t\tstatus <- Status{Err: fmt.Errorf(\"error: failed to create temporary directory: %v\\n\", err)}\n\t}\n\tdefer os.RemoveAll(tmp)\n\n\t// Download the new app\n\tsource := update.SourceURL\n\t// source := fmt.Sprintf(update.SourceURL, update.Version)\n\t// source := fmt.Sprintf(update.SourceURL, \"1.1.11\") // testing\n\tbody, err := get(source)\n\tif err != nil {\n\t\tstatus <- Status{Err: fmt.Errorf(\"error: failed to download new version: %v\\n\", err)}\n\t}\n\n\tos.WriteFile(filepath.Join(tmp, \"assets.zip\"), body, os.ModePerm)\n\tos.Mkdir(filepath.Join(tmp, \"assets\"), os.ModePerm)\n\n\tsource = source + \".checksum.txt\"\n\tbody, err = get(source)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error: failed to download checksum: %v\\n\", err)\n\t}\n\n\tos.WriteFile(filepath.Join(tmp, \"assets.zip.checksum.txt\"), body, os.ModePerm)\n\n\tfilePath := filepath.Join(tmp, \"assets.zip\")                  // path to the file you want to validate\n\tchecksumFile := filepath.Join(tmp, \"assets.zip.checksum.txt\") // path to the checksum file\n\n\t// Step 1: Compute the MD5 checksum of the file\n\tstatus <- Status{Text: \"verifying checksum...\"}\n\tcomputedChecksum, err := computeMD5Checksum(filePath)\n\tif err != nil {\n\t\tstatus <- Status{Err: fmt.Errorf(\"Error computing checksum: %v\", err)}\n\t}\n\n\t// Step 2: Read the checksum from the .checksum.txt file\n\tstoredChecksum, err := readChecksumFromFile(checksumFile)\n\tif err != nil {\n\t\tstatus <- Status{Err: err}\n\t}\n\n\t// Step 3: Compare the computed checksum with the stored checksum\n\tif strings.ToLower(computedChecksum) != strings.ToLower(storedChecksum) {\n\t\tstatus <- Status{Err: fmt.Errorf(\"cannot validate update file (checksum mismatch)\")}\n\t}\n\n\tstatus <- Status{Text: \"extracting update...\"}\n\tif err := unzip(filepath.Join(tmp, \"assets.zip\"), filepath.Join(tmp, \"assets\")); err != nil {\n\t\tstatus <- Status{Err: err}\n\t}\n\n\t// Get any additional assets\n\tif len(update.Assets) > 0 {\n\t\tstatus <- Status{Text: fmt.Sprintf(\"downloading %d additional assets...\", len(update.Assets))}\n\t\tfor _, asset := range update.Assets {\n\t\t\tvar assetURL string\n\t\t\tif !strings.HasPrefix(asset, \"http\") {\n\t\t\t\tassetURL = update.SourceURL\n\t\t\t\t// assetURL = fmt.Sprintf(update.SourceURL, asset)\n\t\t\t} else {\n\t\t\t\tassetURL = asset\n\t\t\t}\n\t\t\tassetBody, err := get(assetURL)\n\t\t\tif err != nil {\n\t\t\t\tstatus <- Status{Err: fmt.Errorf(\"error: failed to download asset: %v\\n\", err)}\n\t\t\t}\n\n\t\t\tassetPath := filepath.Join(tmp, \"assets\", asset)\n\t\t\tos.WriteFile(assetPath, assetBody, os.ModePerm)\n\t\t}\n\t}\n\n\t// Debugging\n\tif verbose {\n\t\ttree(tmp, \"downloaded files (extracted):\")\n\t\tnvmtestcmd := exec.Command(filepath.Join(tmp, \"assets\", \"nvm.exe\"), \"version\")\n\t\tnvmtestcmd.Stdout = os.Stdout\n\t\tnvmtestcmd.Stderr = os.Stderr\n\t\terr = nvmtestcmd.Run()\n\t\tif err != nil {\n\t\t\tfmt.Println(\"error running nvm.exe:\", err)\n\t\t}\n\t}\n\n\t// Backup current version to zip\n\tstatus <- Status{Text: \"applying update...\"}\n\tcurrentExe, _ := os.Executable()\n\tcurrentPath := filepath.Dir(currentExe)\n\tbkp, err := os.MkdirTemp(\"\", \"nvm-backup-*\")\n\tif err != nil {\n\t\tstatus <- Status{Err: fmt.Errorf(\"error: failed to create backup directory: %v\\n\", err)}\n\t}\n\tdefer os.RemoveAll(bkp)\n\n\terr = zipDirectory(currentPath, filepath.Join(bkp, \"backup.zip\"))\n\tif err != nil {\n\t\tstatus <- Status{Err: fmt.Errorf(\"error: failed to create backup: %v\\n\", err)}\n\t}\n\n\tos.MkdirAll(filepath.Join(currentPath, \".update\"), os.ModePerm)\n\tcopyFile(filepath.Join(bkp, \"backup.zip\"), filepath.Join(currentPath, \".update\", \"nvm4w-backup.zip\"))\n\n\t// Copy the new files to the current directory\n\t// copyFile(currentExe, fmt.Sprintf(\"%s.%s.bak\", currentExe, version))\n\tcopyDirContents(filepath.Join(tmp, \"assets\"), currentPath)\n\tcopyFile(filepath.Join(tmp, \"assets\", \"nvm.exe\"), filepath.Join(currentPath, \".update/nvm.exe\"))\n\n\tif verbose {\n\t\tnvmtestcmd := exec.Command(filepath.Join(currentPath, \".update/nvm.exe\"), \"version\")\n\t\tnvmtestcmd.Stdout = os.Stdout\n\t\tnvmtestcmd.Stderr = os.Stderr\n\t\terr = nvmtestcmd.Run()\n\t\tif err != nil {\n\t\t\tstatus <- Status{Err: err}\n\t\t}\n\t}\n\n\t// Debugging\n\tif verbose {\n\t\ttree(currentPath, \"final directory contents:\")\n\t}\n\n\t// Hide the update directory\n\tsetHidden(filepath.Join(currentPath, \".update\"))\n\n\t// If an \"update.exe\" exists, run it\n\tif fsutil.IsExecutable(filepath.Join(tmp, \"assets\", \"update.exe\")) {\n\t\terr = copyFile(filepath.Join(tmp, \"assets\", \"update.exe\"), filepath.Join(currentPath, \".update\", \"update.exe\"))\n\t\tif err != nil {\n\t\t\tstatus <- Status{Err: fmt.Errorf(\"error: failed to copy update.exe: %v\\n\", err)}\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\n\tautoupdate(status)\n\n\treturn nil\n}\n\ntype Status struct {\n\tText   string\n\tErr    error\n\tDone   bool\n\tHelp   bool\n\tCancel bool\n\tWarn   string\n}\n\nfunc (u *Update) Available(sinceVersion string) (string, bool, error) {\n\tcurrentVersion, err := semver.New(sinceVersion)\n\tif err != nil {\n\t\treturn \"\", false, err\n\t}\n\n\tupdateVersion, err := semver.New(u.Version)\n\tif err != nil {\n\t\treturn \"\", false, err\n\t}\n\n\tif currentVersion.LT(updateVersion) {\n\t\treturn u.Version, true, nil\n\t}\n\n\treturn \"\", false, nil\n}\n\nfunc Warn(msg string, colorized ...bool) {\n\tif len(colorized) > 0 && colorized[0] {\n\t\tfmt.Println(warningIcon + \"  \" + highlight(msg))\n\t} else {\n\t\tfmt.Println(strings.ToUpper(msg))\n\t}\n}\n\nfunc Get() (*Update, error) {\n\treturn checkForUpdate(UPDATE_URL)\n}\n\nfunc autoupdate(status chan Status) {\n\tcurrentPath, err := os.Executable()\n\tif err != nil {\n\t\tstatus <- Status{Err: err}\n\t\tfmt.Println(\"error getting updater path:\", err)\n\t\tos.Exit(1)\n\t}\n\n\t// Create temporary directory for the updater script\n\ttempDir := filepath.Dir(currentPath) // Use the same temp dir as the new executable\n\tscriptPath := filepath.Join(tempDir, \"updater.bat\")\n\n\t// Temporary batch file that deletes the directory and the scheduled task\n\ttmp, err := os.MkdirTemp(\"\", \"nvm4w-remove-*\")\n\tif err != nil {\n\t\tstatus <- Status{Err: err}\n\t\tfmt.Printf(\"error creating temporary directory: %v\", err)\n\t\tos.Exit(1)\n\t}\n\n\t// schedule removal of restoration folder for 30 days from now\n\ttempBatchFile := filepath.Join(tmp, \"remove_backup.bat\")\n\tnow := time.Now()\n\tfutureDate := now.AddDate(0, 0, 7)\n\tformattedDate := futureDate.Format(\"01/02/2006\")\n\tbatchContent := fmt.Sprintf(`\n@echo off\nschtasks /delete /tn \"RemoveNVM4WBackup\" /f\nrmdir /s /q \"%s\"\n`, escapeBackslashes(filepath.Join(filepath.Dir(currentPath), \".update\")))\n\n\t// Write the batch file to a temporary location\n\terr = os.WriteFile(tempBatchFile, []byte(batchContent), os.ModePerm)\n\tif err != nil {\n\t\tstatus <- Status{Err: err}\n\t\tfmt.Printf(\"error creating temporary batch file: %v\", err)\n\t\tos.Exit(1)\n\t}\n\n\tupdaterScript := fmt.Sprintf(`@echo off\nsetlocal enabledelayedexpansion\n\necho ========= Update Script Started ========= >> error.log\necho Started updater script with PID %%1 at %%TIME%% >> error.log\necho Source: %%~2 >> error.log\necho Target: %%~3 >> error.log\n\n:wait\ntimeout /t 1 /nobreak >nul\ntasklist /fi \"PID eq %%1\" 2>nul | find \"%%1\" >nul\nif not errorlevel 1 (\n\techo Waiting for PID %%1 to exit at %%TIME%%... >> error.log\n\tgoto :wait\n)\n\necho ========= Starting Copy Operation ========= >> error.log\necho Checking if source (%%~2) exists... >> error.log\nif not exist \"%%~2\" (\n\techo ERROR: Source file does not exist: %%~2 >> error.log\n\texit /b 1\n)\necho Source file exists >> error.log\n\ndel \"%%~3\" >> error.log\n\necho Checking if target location is writable... >> error.log\necho Test > \"%%~dp3test.txt\" 2>>error.log\nif errorlevel 1 (\n\techo ERROR: Target location is not writable: %%~dp3 >> error.log\n\texit /b 1\n)\ndel \"%%~dp3test.txt\"\necho Target location is writable >> error.log\n\necho Attempting copy at %%TIME%%... >> error.log\necho Running: copy /y \"%%~2\" \"%%~3\" >> error.log\ncopy /y \"%%~2\" \"%%~3\" >> error.log 2>&1\nif errorlevel 1 (\n\techo ERROR: Copy failed with error level %%errorlevel%% >> error.log\n\texit /b %%errorlevel%%\n)\n\necho Verifying copy... >> error.log\nif not exist \"%%~3\" (\n\techo ERROR: Target file does not exist after copy: %%~3 >> error.log\n\texit /b 1\n)\n\ndel \"%%~2\" >> error.log\nif exist \"%%~2\" (\n\techo ERROR: Source file still exists after deletion: %%~2 >> error.log\n\texit /b 1\n)\n\n:: Schedule the task to delete the directory\necho schtasks /create /tn \"RemoveNVM4WBackup\" /tr \"cmd.exe /c %s\" /sc once /sd %s /st 12:00 /f >> error.log\nschtasks /create /tn \"RemoveNVM4WBackup\" /tr \"cmd.exe /c %s\" /sc once /sd %s /st 12:00 /f\nif not errorlevel 0 (\n\techo ERROR: Failed to create scheduled task: exit code: %%errorlevel%% >> error.log\n\texit /b %%errorlevel%%\n)\n\necho Update complete >> error.log\n\ndel error.log\n\ndel \"%%~f0\"\nstart \"nvm://launch?action=upgrade_notify\"\nexit /b 0\n`, escapeBackslashes(tempBatchFile), formattedDate, escapeBackslashes(tempBatchFile), formattedDate)\n\n\terr = os.WriteFile(scriptPath, []byte(updaterScript), os.ModePerm) // Use standard Windows file permissions\n\tif err != nil {\n\t\tstatus <- Status{Err: err}\n\t\tfmt.Printf(\"error creating updater script: %v\", err)\n\t\tos.Exit(1)\n\t}\n\n\t// Start the updater script\n\tcmd := exec.Command(scriptPath, fmt.Sprintf(\"%d\", os.Getpid()), filepath.Join(tempDir, \".update\", \"nvm.exe\"), currentPath)\n\terr = cmd.Start()\n\tif err != nil {\n\t\tstatus <- Status{Err: err}\n\t\tfmt.Printf(\"error starting updater script: %v\", err)\n\t\tos.Exit(1)\n\t}\n\n\t// Exit the current process (delay for cleanup)\n\ttime.Sleep(300 * time.Millisecond)\n\tstatus <- Status{Text: \"restarting app...\", Done: true}\n\ttime.Sleep(2 * time.Second)\n\tos.Exit(0)\n}\n\nfunc escapeBackslashes(path string) string {\n\treturn strings.Replace(path, \"\\\\\", \"\\\\\\\\\", -1)\n}\n\nfunc tree(dir string, title ...string) {\n\tif len(title) > 0 {\n\t\tfmt.Println(\"\\n\" + highlight(title[0]))\n\t}\n\tcmd := exec.Command(\"tree\", dir, \"/F\")\n\tcmd.Stdout = os.Stdout\n\tcmd.Stderr = os.Stderr\n\tif err := cmd.Run(); err != nil {\n\t\tfmt.Println(\"Error executing command:\", err)\n\t}\n}\n\nfunc get(url string, verbose ...bool) ([]byte, error) {\n\tif len(verbose) == 0 || verbose[0] {\n\t\tfmt.Printf(\"  GET %s\\n\", url)\n\t}\n\n\tclient := &http.Client{}\n\treq, err := http.NewRequest(\"GET\", url, nil)\n\tif err != nil {\n\t\treturn []byte{}, err\n\t}\n\treq.Header.Set(\"User-Agent\", \"nvm-windows\")\n\treq.Header.Set(\"Cache-Control\", \"no-cache\")\n\treq.Header.Set(\"Pragma\", \"no-cache\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn []byte{}, fmt.Errorf(\"error: received status code %d\", resp.StatusCode)\n\t}\n\n\treturn io.ReadAll(resp.Body)\n}\n\nfunc checkForUpdate(url string) (*Update, error) {\n\tu := Update{Assets: []string{}, Warnings: []string{}, VersionWarnings: []string{}}\n\tr := Release{}\n\n\t// Make the HTTP GET request\n\tutility.DebugLogf(\"checking for updates at %s\", url)\n\tbody, err := get(url, false)\n\tif err != nil {\n\t\treturn &u, fmt.Errorf(\"error: reading response body: %v\", err)\n\t}\n\n\t// Parse JSON into the struct\n\tutility.DebugLogf(\"Received:\\n%s\", string(body))\n\terr = json.Unmarshal(body, &r)\n\tif err != nil {\n\t\treturn &u, fmt.Errorf(\"error: parsing release: %v\", err)\n\t}\n\n\tu.Version = r.Version\n\tutility.DebugLogf(\"latest version: %s\", u.Version)\n\n\t// Comment the next line when development is complete\n\t// u.Version = \"2.0.0\"\n\tfor _, asset := range r.Assets {\n\t\tif value, exists := asset[\"name\"]; exists && value.(string) == \"update.exe\" {\n\t\t\tu.Assets = append(u.Assets, value.(string))\n\t\t}\n\t\tif value, exists := asset[\"name\"]; exists && value.(string) == \"nvm-noinstall.zip\" {\n\t\t\tu.SourceURL = asset[\"browser_download_url\"].(string)\n\t\t}\n\t}\n\n\tutility.DebugLogf(\"source URL: %s\", u.SourceURL)\n\tutility.DebugLogf(\"assets: %v\", u.Assets)\n\n\t// Get alerts\n\tutility.DebugLogf(\"downloading alerts from %s\", ALERTS_URL)\n\tbody, err = get(ALERTS_URL, false)\n\tif err != nil {\n\t\tutility.DebugLogf(\"alert download error: %v\", err)\n\t\treturn &u, err\n\t}\n\n\tutility.DebugLogf(\"Received:\\n%s\", string(body))\n\n\tvar alerts map[string][]interface{}\n\terr = json.Unmarshal(body, &alerts)\n\tif err != nil {\n\t\tutility.DebugLogf(\"alert parsing error: %v\", err)\n\t}\n\n\tif value, exists := alerts[\"all\"]; exists {\n\t\tfor _, warning := range value {\n\t\t\twarn := warning.(map[string]interface{})\n\t\t\tif v, exists := warn[\"message\"]; exists {\n\t\t\t\tu.Warnings = append(u.Warnings, v.(string))\n\t\t\t}\n\t\t}\n\t}\n\n\tif value, exists := alerts[u.Version]; exists {\n\t\tutility.DebugLogf(\"version warnings exist for %v\\n%v\", u.Version, value)\n\t\tfor _, warning := range value {\n\t\t\tutility.DebugLogf(\"warning: %v\", warning)\n\t\t\twarn := warning.(map[string]interface{})\n\t\t\tif v, exists := warn[\"message\"]; exists {\n\t\t\t\tu.VersionWarnings = append(u.VersionWarnings, v.(string))\n\t\t\t}\n\t\t}\n\t}\n\n\tutility.DebugLogf(\"warnings: %v\", u.Warnings)\n\tutility.DebugLogf(\"version warnings: %v\", u.VersionWarnings)\n\n\treturn &u, nil\n}\n\nfunc EnableVirtualTerminalProcessing() error {\n\t// Get the handle to the standard output\n\thandle := windows.Stdout\n\n\t// Retrieve the current console mode\n\tvar mode uint32\n\tif err := windows.GetConsoleMode(handle, &mode); err != nil {\n\t\treturn err\n\t}\n\n\t// Enable the virtual terminal processing mode\n\tmode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING\n\tif err := windows.SetConsoleMode(handle, mode); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc highlight(message string) string {\n\treturn fmt.Sprintf(\"%s%s%s\", yellow, message, reset)\n}\n\n// Unzip function extracts a zip file to a specified directory\nfunc unzip(src string, dest string) error {\n\t// Open the zip archive for reading\n\tr, err := zip.OpenReader(src)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer r.Close()\n\n\t// Iterate over each file in the zip archive\n\tfor _, f := range r.File {\n\t\t// Build the path for each file in the destination directory\n\t\tfpath := filepath.Join(dest, f.Name)\n\n\t\t// Check if the file is a directory\n\t\tif f.FileInfo().IsDir() {\n\t\t\t// Create directory if it doesn't exist\n\t\t\tif err := os.MkdirAll(fpath, os.ModePerm); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\t// Create directories leading to the file if they don't exist\n\t\tif err := os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// Open the file in the zip archive\n\t\trc, err := f.Open()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer rc.Close()\n\n\t\t// Create the destination file\n\t\toutFile, err := os.Create(fpath)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer outFile.Close()\n\n\t\t// Copy the file contents from the archive to the destination file\n\t\t_, err = io.Copy(outFile, rc)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// function to compute the MD5 checksum of a file\nfunc computeMD5Checksum(filePath string) (string, error) {\n\tfile, err := os.Open(filePath)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer file.Close()\n\n\thasher := md5.New()\n\t_, err = io.Copy(hasher, file)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\t// Return the hex string representation of the MD5 hash\n\treturn fmt.Sprintf(\"%x\", hasher.Sum(nil)), nil\n}\n\n// function to read the checksum from the .checksum.txt file\nfunc readChecksumFromFile(checksumFile string) (string, error) {\n\tfile, err := os.Open(checksumFile)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer file.Close()\n\n\tvar checksum string\n\t_, err = fmt.Fscan(file, &checksum)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn checksum, nil\n}\n\nfunc copyFile(src, dst string) error {\n\t// Open the source file\n\tsourceFile, err := os.Open(src)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to open source file: %v\", err)\n\t}\n\tdefer sourceFile.Close()\n\n\t// Create the destination file\n\tdestinationFile, err := os.Create(dst)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create destination file: %v\", err)\n\t}\n\tdefer destinationFile.Close()\n\n\t// Copy contents from the source file to the destination file\n\t_, err = io.Copy(destinationFile, sourceFile)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to copy file: %v\", err)\n\t}\n\n\t// Optionally, copy file permissions (this can be skipped if not needed)\n\tsourceInfo, err := os.Stat(src)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get source file info: %v\", err)\n\t}\n\n\terr = os.Chmod(dst, sourceInfo.Mode())\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to set file permissions: %v\", err)\n\t}\n\n\treturn nil\n}\n\n// copyDirContents copies all the contents (files and subdirectories) of a source directory to a destination directory.\nfunc copyDirContents(srcDir, dstDir string) error {\n\t// Ensure destination directory exists\n\terr := os.MkdirAll(dstDir, 0755)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create destination directory %s: %v\", dstDir, err)\n\t}\n\n\t// Walk through the source directory recursively\n\terr = filepath.Walk(srcDir, func(srcPath string, info os.FileInfo, err error) error {\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error accessing %s: %v\", srcPath, err)\n\t\t}\n\n\t\t// Construct the corresponding path in the destination directory\n\t\trelPath, err := filepath.Rel(srcDir, srcPath)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to get relative path for %s: %v\", srcPath, err)\n\t\t}\n\n\t\tdstPath := filepath.Join(dstDir, relPath)\n\n\t\t// If it's a directory, ensure it's created in the destination\n\t\tif info.IsDir() {\n\t\t\treturn os.MkdirAll(dstPath, info.Mode())\n\t\t}\n\n\t\t// If it's a file, copy it\n\t\treturn copyFile(srcPath, dstPath)\n\t})\n\n\treturn err\n}\n\n// zipDirectory zips the contents of a directory.\nfunc zipDirectory(sourceDir, outputZip string) error {\n\t// Create the zip file.\n\tzipFile, err := os.Create(outputZip)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer zipFile.Close()\n\n\t// Create a new zip writer.\n\tzipWriter := zip.NewWriter(zipFile)\n\tdefer zipWriter.Close()\n\n\t// Walk through the directory.\n\treturn filepath.Walk(sourceDir, func(path string, info os.FileInfo, err error) error {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// Get the relative path.\n\t\trelPath, err := filepath.Rel(sourceDir, path)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// Skip the directory itself but include subdirectories.\n\t\tif info.IsDir() {\n\t\t\tif relPath == \".\" {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\t// Add a trailing slash for directories in the zip archive.\n\t\t\trelPath += \"/\"\n\t\t}\n\n\t\t// Create a zip header.\n\t\theader, err := zip.FileInfoHeader(info)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\theader.Name = relPath\n\t\tif info.IsDir() {\n\t\t\theader.Method = zip.Store\n\t\t} else {\n\t\t\theader.Method = zip.Deflate\n\t\t}\n\n\t\t// Create a writer for the file in the zip archive.\n\t\twriter, err := zipWriter.CreateHeader(header)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// If the file is not a directory, copy its contents into the archive.\n\t\tif !info.IsDir() {\n\t\t\tfile, err := os.Open(path)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdefer file.Close()\n\t\t\t_, err = io.Copy(writer, file)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t})\n}\n\nfunc setHidden(path string) error {\n\t// Convert the path to a UTF-16 encoded string\n\tlpFileName, err := syscall.UTF16PtrFromString(path)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to encode path: %w\", err)\n\t}\n\n\t// Call the Windows API function\n\tret, _, err := syscall.NewLazyDLL(\"kernel32.dll\").\n\t\tNewProc(\"SetFileAttributesW\").\n\t\tCall(\n\t\t\tuintptr(unsafe.Pointer(lpFileName)),\n\t\t\tuintptr(FILE_ATTRIBUTE_HIDDEN),\n\t\t)\n\n\t// Check the result\n\tif ret == 0 {\n\t\treturn fmt.Errorf(\"failed to set hidden attribute: %w\", err)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "src/utility/logging.go",
    "content": "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 exe string\nvar path string\n\nconst (\n\t// Enable virtual terminal processing on Windows (required to interpret ANSI escape codes)\n\tenableVirtualTerminalProcessing = 0x0004\n\tBOLD                            = \"\\033[38;2;255;165;0m\"\n\tTEXT                            = \"\\033[38;2;255;200;100m\"\n\tRESET                           = \"\\033[0m\"\n)\n\nfunc enableANSI() {\n\tkernel32 := syscall.NewLazyDLL(\"kernel32.dll\")\n\tsetConsoleMode := kernel32.NewProc(\"SetConsoleMode\")\n\tstdout := syscall.Stdout\n\t// Get the current console mode\n\tvar mode uint32\n\terr := syscall.GetConsoleMode(stdout, &mode)\n\tif err != nil {\n\t\tfmt.Println(\"Error getting console mode:\", err)\n\t\treturn\n\t}\n\t// Enable virtual terminal processing\n\tmode |= enableVirtualTerminalProcessing\n\t_, _, err = setConsoleMode.Call(uintptr(stdout), uintptr(mode))\n\tif err != nil && err.Error() != \"The operation completed successfully.\" {\n\t\tfmt.Println(\"Error enabling ANSI:\", err)\n\t}\n}\n\nfunc bold(text string) string {\n\treturn BOLD + text + RESET\n}\n\nfunc text(txt string) string {\n\treturn TEXT + txt + RESET\n}\n\nfunc EnableDebugLogs() {\n\tdebug = true\n\texe, _ = os.Executable()\n\tpath = filepath.Join(filepath.Dir(exe), \"..\")\n\tenableANSI()\n}\n\nfunc DebugLog(args ...interface{}) {\n\tif debug {\n\t\t_, file, line, _ := runtime.Caller(1)\n\t\tfor _, arg := range args {\n\t\t\tfmt.Printf(bold(\"[DEBUG] %v:%v\")+\" \"+text(\"%v\")+\"\\n\", strings.Replace(filepath.ToSlash(file), filepath.ToSlash(path), \"..\", 1), line, arg)\n\t\t}\n\t}\n}\n\nfunc DebugLogf(tpl string, args ...interface{}) {\n\tif debug {\n\t\t_, file, line, _ := runtime.Caller(1)\n\t\tfmt.Printf(bold(\"[DEBUG] %v:%v\")+\" \"+text(\"%v\")+\"\\n\", strings.Replace(filepath.ToSlash(file), filepath.ToSlash(path), \"..\", 1), line, fmt.Sprintf(tpl, args...))\n\t}\n}\n\nfunc DebugFn(fn func()) {\n\tif debug {\n\t\tfn()\n\t}\n}\n"
  },
  {
    "path": "src/utility/rename.go",
    "content": "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 := filepath.VolumeName(old)\n\tnew_drive := filepath.VolumeName(new)\n\n\tif old_drive == new_drive {\n\t\treturn os.Rename(old, new)\n\t}\n\n\t// Get file or directory info\n\tinfo, err := os.Stat(old)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to stat source: %w\", err)\n\t}\n\n\t// If old is a directory, copy recursively\n\tif info.IsDir() {\n\t\terr = copyDir(old, new)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to copy directory: %w\", err)\n\t\t}\n\t} else {\n\t\t// Otherwise, copy a single file\n\t\terr = copyFile(old, new)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to copy file: %w\", err)\n\t\t}\n\t}\n\n\t// Remove the original source\n\terr = os.RemoveAll(old)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to remove source: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// copyFile copies a single file from source (old) to destination (new).\nfunc copyFile(old, new string) error {\n\tsrcFile, err := os.Open(old)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to open source file: %w\", err)\n\t}\n\tdefer srcFile.Close()\n\n\t// Ensure destination directory exists\n\tdestDir := filepath.Dir(new)\n\terr = os.MkdirAll(destDir, os.ModePerm)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create destination directory: %w\", err)\n\t}\n\n\tdestFile, err := os.Create(new)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create destination file: %w\", err)\n\t}\n\tdefer destFile.Close()\n\n\t_, err = io.Copy(destFile, srcFile)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to copy data: %w\", err)\n\t}\n\n\t// Copy file permissions\n\tinfo, err := srcFile.Stat()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get source file info: %w\", err)\n\t}\n\terr = os.Chmod(new, info.Mode())\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to set permissions on destination file: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// copyDir recursively copies a directory from old path to new path.\nfunc copyDir(old, new string) error {\n\tentries, err := os.ReadDir(old)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read source directory: %w\", err)\n\t}\n\n\t// Ensure destination directory exists\n\terr = os.MkdirAll(new, os.ModePerm)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create destination directory: %w\", err)\n\t}\n\n\tfor _, entry := range entries {\n\t\tsrcPath := filepath.Join(old, entry.Name())\n\t\tdestPath := filepath.Join(new, entry.Name())\n\n\t\tif entry.IsDir() {\n\t\t\terr = copyDir(srcPath, destPath)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to copy subdirectory: %w\", err)\n\t\t\t}\n\t\t} else {\n\t\t\terr = copyFile(srcPath, destPath)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to copy file: %w\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "src/web/web.go",
    "content": "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\"\n\t\"os/exec\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"syscall\"\n\n\t\"nvm/utility\"\n\n\t\"archive/zip\"\n\n\t\"github.com/blang/semver\"\n\tfs \"github.com/coreybutler/go-fsutil\"\n)\n\nvar nvmversion = \"\"\nvar client = &http.Client{}\nvar nodeBaseAddress = \"https://nodejs.org/dist/\"\nvar npmBaseAddress = \"https://github.com/npm/cli/archive/\"\n\n// var oldNpmBaseAddress = \"https://github.com/npm/npm/archive/\"\n\nfunc SetProxy(p string, verifyssl bool) {\n\tif p != \"\" && p != \"none\" {\n\t\tproxyUrl, _ := url.Parse(p)\n\t\tclient = &http.Client{Transport: &http.Transport{Proxy: http.ProxyURL(proxyUrl), TLSClientConfig: &tls.Config{InsecureSkipVerify: verifyssl}}}\n\t} else {\n\t\tclient = &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: verifyssl}}}\n\t}\n}\n\nfunc SetMirrors(node_mirror string, npm_mirror string) {\n\tif node_mirror != \"\" && node_mirror != \"none\" {\n\t\tnodeBaseAddress = node_mirror\n\t\tif strings.ToLower(nodeBaseAddress[0:4]) != \"http\" {\n\t\t\tnodeBaseAddress = \"http://\" + nodeBaseAddress\n\t\t}\n\t\tif !strings.HasSuffix(nodeBaseAddress, \"/\") {\n\t\t\tnodeBaseAddress = nodeBaseAddress + \"/\"\n\t\t}\n\t}\n\tif npm_mirror != \"\" && npm_mirror != \"none\" {\n\t\tnpmBaseAddress = npm_mirror\n\t\tif strings.ToLower(npmBaseAddress[0:4]) != \"http\" {\n\t\t\tnpmBaseAddress = \"http://\" + npmBaseAddress\n\t\t}\n\t\tif !strings.HasSuffix(npmBaseAddress, \"/\") {\n\t\t\tnpmBaseAddress = npmBaseAddress + \"/\"\n\t\t}\n\t}\n}\n\nfunc GetFullNodeUrl(path string) string {\n\treturn nodeBaseAddress + path\n}\n\nfunc GetFullNpmUrl(path string) string {\n\treturn npmBaseAddress + path\n}\n\nfunc IsLocalIPv6() (bool, error) {\n\tconn, err := net.Dial(\"tcp\", \"[::1]:80\")\n\tif err != nil {\n\t\tif strings.Contains(strings.ToLower(err.Error()), \"no connection\") {\n\t\t\treturn false, nil\n\t\t}\n\n\t\treturn false, err\n\t}\n\tdefer conn.Close()\n\n\treturn true, nil\n\t// addrs, err := net.InterfaceAddrs()\n\t// if err != nil {\n\t// \treturn false, err\n\t// }\n\n\t// for _, addr := range addrs {\n\t// \tfmt.Println(addr.String())\n\t// \tif strings.Contains(addr.String(), \":\") {\n\t// \t\treturn true, nil\n\t// \t}\n\t// }\n\n\t// return false, nil\n}\n\n// Returns whether the address can be pinged and whether it is using IPv6 or not\nfunc Ping(url string) bool {\n\treq, err := http.NewRequest(\"HEAD\", url, nil)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn false\n\t}\n\n\treq.Header.Set(\"User-Agent\", \"NVM for Windows\")\n\n\tresponse, err := client.Do(req)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tif response.StatusCode == 200 {\n\t\treturn true\n\t}\n\n\treturn false\n}\n\nfunc Download(url string, target string, version string) bool {\n\toutput, err := os.Create(target)\n\tif err != nil {\n\t\tfmt.Println(\"Error while creating\", target, \"-\", err)\n\t\treturn false\n\t}\n\tdefer output.Close()\n\n\treq, err := http.NewRequest(\"GET\", url, nil)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn false\n\t}\n\n\treq.Header.Set(\"User-Agent\", fmt.Sprintf(\"NVM for Windows %s\", nvmversion))\n\n\tresponse, err := client.Do(req)\n\tif err != nil {\n\t\tfmt.Println(\"Error while downloading\", url, \"-\", err)\n\t\treturn false\n\t}\n\tdefer response.Body.Close()\n\tc := make(chan os.Signal, 2)\n\tsignal.Notify(c, os.Interrupt, syscall.SIGTERM)\n\tgo func() {\n\t\t<-c\n\t\tfmt.Println(\"Download interrupted. Rolling back...\")\n\t\toutput.Close()\n\t\tresponse.Body.Close()\n\t\tvar err error\n\t\tif strings.Contains(target, \"node\") {\n\t\t\terr = os.RemoveAll(os.Getenv(\"NVM_HOME\") + \"\\\\v\" + version)\n\t\t} else {\n\t\t\terr = os.Remove(target)\n\t\t}\n\t\tif err != nil {\n\t\t\tfmt.Println(\"Error while rolling back\", err)\n\t\t}\n\t\tos.Exit(1)\n\t}()\n\tvar body []byte\n\tif response.StatusCode != 200 {\n\t\tbody, err = ioutil.ReadAll(response.Body)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"Failed to read response body: \" + err.Error())\n\t\t}\n\t} else {\n\t\t_, err = io.Copy(output, response.Body)\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"Error while downloading %s: %v\\n\", url, err)\n\t\t}\n\t}\n\n\tredirect := response.Header.Get(\"Location\")\n\n\tswitch response.StatusCode {\n\tcase 300:\n\t\tif len(redirect) > 0 && redirect != url {\n\t\t\treturn Download(redirect, target, version)\n\t\t}\n\n\t\tif strings.Contains(url, \"/npm/cli/archive/v6.14.17.zip\") {\n\t\t\treturn Download(\"https://github.com/npm/cli/archive/refs/tags/v6.14.17.zip\", target, version)\n\t\t}\n\n\t\tfmt.Printf(\"\\n\\nREMOTE SERVER FAILURE\\n\\n---\\nGET %v --> %v\\n\\n\", url, response.StatusCode)\n\t\tfor key, val := range response.Header {\n\t\t\tfmt.Printf(\"%v: %v\\n\", key, val)\n\t\t}\n\t\tif len(body) > 0 {\n\t\t\tfmt.Printf(\"\\n%s\", body)\n\t\t}\n\t\tfmt.Println(\"\\n---\\n\\n\")\n\n\t\treturn false\n\tcase 302:\n\t\tfallthrough\n\tcase 307:\n\t\tfmt.Println(\"Redirecting to \" + redirect)\n\t\treturn Download(redirect, target, version)\n\tcase 200:\n\t\t// No processing necessary for successful response\n\tdefault:\n\t\tfmt.Println(\"Download failed. Rolling Back.\")\n\t\terr := os.Remove(target)\n\t\tif err != nil {\n\t\t\tfmt.Println(target)\n\t\t\tfmt.Println(\"Rollback failed.\", err)\n\t\t}\n\t\treturn false\n\t}\n\n\treturn true\n}\n\nfunc GetNodeJS(root string, v string, a string, append bool) bool {\n\tutility.DebugLogf(\"running GetNodeJS with root: %v, v%v, arch: %v, append: %v\", root, v, a, append)\n\ta = arch.Validate(a)\n\n\tvpre := \"\"\n\tvers := strings.Fields(strings.Replace(v, \".\", \" \", -1))\n\tmain, _ := strconv.ParseInt(vers[0], 0, 0)\n\n\tif a == \"32\" {\n\t\tif main > 0 {\n\t\t\tvpre = \"win-x86/\"\n\t\t} else {\n\t\t\tvpre = \"\"\n\t\t}\n\t} else if a == \"64\" {\n\t\tif main > 0 {\n\t\t\tvpre = \"win-x64/\"\n\t\t} else {\n\t\t\tvpre = \"x64/\"\n\t\t}\n\t} else if a == \"arm64\" {\n\t\tif main > 0 {\n\t\t\tvpre = \"win-arm64/\"\n\t\t} else {\n\t\t\tvpre = \"arm64/\"\n\t\t}\n\t}\n\n\turl := getNodeUrl(v, vpre, a, append)\n\n\tutility.DebugLogf(\"download url: %v\", url)\n\n\tif url == \"\" {\n\t\t//No url should mean this version/arch isn't available\n\t\tfmt.Println(\"Node.js v\" + v + \" \" + a + \"bit isn't available right now.\")\n\t} else {\n\t\tfileName := root + \"\\\\v\" + v + \"\\\\node\" + a + \".exe\"\n\t\tif strings.HasSuffix(url, \".zip\") {\n\t\t\tfileName = root + \"\\\\v\" + v + \"\\\\node.zip\"\n\t\t}\n\n\t\tfmt.Println(\"Downloading node.js version \" + v + \" (\" + a + \"-bit)... \")\n\n\t\tif Download(url, fileName, v) {\n\t\t\tutility.DebugLog(\"download succeeded\")\n\t\t\t// Extract the zip file\n\t\t\tif strings.HasSuffix(url, \".zip\") {\n\t\t\t\tfmt.Println(\"Extracting node and npm...\")\n\t\t\t\tutility.DebugLogf(\"extracting %v to %v\", fileName, root+\"\\\\v\"+v)\n\t\t\t\terr := unzip(fileName, root+\"\\\\v\"+v)\n\t\t\t\tif err != nil {\n\t\t\t\t\tfmt.Println(\"Error extracting from Node archive: \" + err.Error())\n\n\t\t\t\t\terr = os.Remove(fileName)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Printf(\"Failed to remove %v after failed extraction. Please remove manually.\", fileName)\n\t\t\t\t\t}\n\t\t\t\t\tutility.DebugLogf(\"removed %v\", fileName)\n\n\t\t\t\t\treturn false\n\t\t\t\t}\n\n\t\t\t\terr = os.Remove(fileName)\n\t\t\t\tif err != nil {\n\t\t\t\t\tfmt.Printf(\"Failed to remove %v after successful extraction. Please remove manually.\", fileName)\n\t\t\t\t}\n\t\t\t\tutility.DebugLogf(\"removed %v\", fileName)\n\n\t\t\t\tzip := root + \"\\\\v\" + v + \"\\\\\" + strings.Replace(filepath.Base(url), \".zip\", \"\", 1)\n\t\t\t\tutility.DebugLogf(\"moving %v to %v\", zip, root+\"\\\\v\"+v)\n\t\t\t\terr = fs.Move(zip, root+\"\\\\v\"+v, true)\n\t\t\t\tif err != nil {\n\t\t\t\t\tfmt.Println(\"ERROR moving file: \" + err.Error())\n\t\t\t\t}\n\t\t\t\tutility.DebugLog(\"move succeeded\")\n\n\t\t\t\terr = os.RemoveAll(zip)\n\t\t\t\tif err != nil {\n\t\t\t\t\tfmt.Printf(\"Failed to remove %v after successful extraction. Please remove manually.\", zip)\n\t\t\t\t}\n\t\t\t\tutility.DebugLogf(\"removed %v\", zip)\n\n\t\t\t\tutility.DebugFn(func() {\n\t\t\t\t\tcmd := exec.Command(\"cmd\", \"/C\", \"dir\", root+\"\\\\v\"+v)\n\t\t\t\t\tout, err := cmd.CombinedOutput()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tutility.DebugLog(err.Error())\n\t\t\t\t\t} else {\n\t\t\t\t\t\tutility.DebugLog(string(out))\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\t\t\tfmt.Println(\"Complete\")\n\t\t\treturn true\n\t\t} else {\n\t\t\tutility.DebugLog(\"download failed\")\n\t\t\treturn false\n\t\t}\n\t}\n\treturn false\n\n}\n\nfunc GetNpm(root string, v string) bool {\n\turl := GetFullNpmUrl(\"v\" + v + \".zip\")\n\n\t// temp directory to download the .zip file\n\ttempDir := root + \"\\\\temp\"\n\n\tutility.DebugLogf(\"downloading npm from %v to %v\", url, tempDir)\n\n\t// if the temp directory doesn't exist, create it\n\tif !file.Exists(tempDir) {\n\t\tfmt.Println(\"Creating \" + tempDir + \"\\n\")\n\t\terr := os.Mkdir(tempDir, os.ModePerm)\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\tfileName := tempDir + \"\\\\\" + \"npm-v\" + v + \".zip\"\n\n\tfmt.Printf(\"Downloading npm version \" + v + \"... \")\n\tif Download(url, fileName, v) {\n\t\tutility.DebugLog(\"npm download succeeded\")\n\t\tfmt.Printf(\"Complete\\n\")\n\t\treturn true\n\t} else {\n\t\tutility.DebugLog(\"npm download failed\")\n\t\treturn false\n\t}\n}\n\nfunc GetRemoteTextFile(url string) (string, error) {\n\tresponse, httperr := client.Get(url)\n\tif httperr != nil {\n\t\treturn \"\", fmt.Errorf(\"Could not retrieve %v: %v\", url, httperr)\n\t}\n\n\tif response.StatusCode != 200 {\n\t\treturn \"\", fmt.Errorf(\"Error retrieving \\\"%s\\\": HTTP Status %v\\n\", url, response.StatusCode)\n\t}\n\n\tdefer response.Body.Close()\n\n\tcontents, readerr := ioutil.ReadAll(response.Body)\n\tif readerr != nil {\n\t\treturn \"\", fmt.Errorf(\"error reading HTTP request body: %v\", readerr)\n\t}\n\n\treturn string(contents), nil\n}\n\nfunc IsNode64bitAvailable(v string) bool {\n\tif v == \"latest\" {\n\t\treturn true\n\t}\n\n\t// Anything below version 8 doesn't have a 64 bit version\n\tvers := strings.Fields(strings.Replace(v, \".\", \" \", -1))\n\tmain, _ := strconv.ParseInt(vers[0], 0, 0)\n\tminor, _ := strconv.ParseInt(vers[1], 0, 0)\n\tif main == 0 && minor < 8 {\n\t\treturn false\n\t}\n\n\t// TODO: fixme. Assume a 64 bit version exists\n\treturn true\n}\n\nfunc IsNodeArm64bitAvailable(v string) bool {\n\tif v == \"latest\" {\n\t\treturn true\n\t}\n\n\t// Anything below version 19.9 doesn't have a arm64 bit version\n\tvers := strings.Fields(strings.Replace(v, \".\", \" \", -1))\n\tmain, _ := strconv.ParseInt(vers[0], 0, 0)\n\tminor, _ := strconv.ParseInt(vers[1], 0, 0)\n\tfmt.Println(\"main \" + strconv.FormatInt(main, 10) + \" minor \" + strconv.FormatInt(minor, 10))\n\tif main < 19 {\n\t\treturn false\n\t}\n\tif main == 19 && minor < 9 {\n\t\treturn false\n\t}\n\n\t// TODO: fixme. Assume a 64 bit version exists\n\treturn true\n}\n\nfunc getNodeUrl(v string, vpre string, arch string, append bool) string {\n\ta := \"x86\"\n\tif arch == \"arm64\" {\n\t\ta = \"arm64\"\n\t}\n\tif arch == \"64\" {\n\t\ta = \"x64\"\n\t}\n\n\t//url := \"http://nodejs.org/dist/v\"+v+\"/\" + vpre + \"/node.exe\"\n\turl := GetFullNodeUrl(\"v\" + v + \"/\" + vpre + \"node.exe\")\n\n\tif !append {\n\t\tversion, err := semver.Make(v)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"Node.js v\" + v + \" \" + a + \"bit isn't available right now.\")\n\t\t\tfmt.Println(err.Error())\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\tcorepack, _ := semver.Make(\"16.9.0\")\n\n\t\tif version.GTE(corepack) {\n\t\t\turl = GetFullNodeUrl(\"v\" + v + \"/node-v\" + v + \"-win-\" + a + \".zip\")\n\t\t}\n\t}\n\n\t// Check online to see if a 64 bit version exists\n\t_, err := client.Head(url)\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\treturn url\n}\n\nfunc unzip(src, dest string) error {\n\tr, err := zip.OpenReader(src)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := r.Close(); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}()\n\n\tos.MkdirAll(dest, 0755)\n\n\t// Closure to address file descriptors issue with all the deferred .Close() methods\n\textractAndWriteFile := func(f *zip.File) error {\n\t\trc, err := f.Open()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := rc.Close(); err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t}()\n\n\t\tpath := filepath.Join(dest, f.Name)\n\n\t\t// Check for ZipSlip (Directory traversal)\n\t\tif !strings.HasPrefix(path, filepath.Clean(dest)+string(os.PathSeparator)) {\n\t\t\treturn fmt.Errorf(\"illegal file path: %s\", path)\n\t\t}\n\n\t\tif f.FileInfo().IsDir() {\n\t\t\tos.MkdirAll(path, f.Mode())\n\t\t} else {\n\t\t\tos.MkdirAll(filepath.Dir(path), f.Mode())\n\t\t\tf, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdefer func() {\n\t\t\t\tif err := f.Close(); err != nil {\n\t\t\t\t\tpanic(err)\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\t_, err = io.Copy(f, rc)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\n\tfor _, f := range r.File {\n\t\terr := extractAndWriteFile(f)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  }
]