coreybutler / nvm-windows

A node.js version management utility for Windows. Ironically written in Go.
MIT License
37.28k stars 3.32k forks source link

[Issue]: nvm.exe hangs when started by TeamCity because Stdout is not a character mode device #1119

Closed RagingTonberry closed 6 months ago

RagingTonberry commented 6 months ago

What happened?

I am using TeamCity, and I would like to swap node versions with nvm4w before and after the process runs for various reasons.

When running in TC's context, the nvm.exe process hangs indefinitely. Code path was identied and will be below.

What did you expect to happen?

I expected it to use the specified version.

Version

1.1.11 or newer (Default)

Which version of Windows?

Windows 10 (Actually Windows Server 2022, but potato potahto)

Which locale?

English (Default)

Which shell are you running NVM4W in?

Command Prompt

User Permissions?

Administrative Privileges, Elevated

Is Developer Mode enabled?

No (Default)

Relevant log/console output

No response

Debug Output

Running NVM for Windows with administrator privileges.

Administrator: Windows PowerShell
Windows Version: 10.0 (Build 20348)

NVM4W Version:      1.1.12
NVM4W Path:         C:\Users\janadmin\AppData\Roaming\nvm\nvm-i386.exe
NVM4W Settings:     C:\Users\janadmin\AppData\Roaming\nvm\settings.txt
NVM_HOME:           C:\Users\janadmin\AppData\Roaming\nvm
NVM_SYMLINK:        C:\Program Files\nodejs
Node Installations: C:\Users\janadmin\AppData\Roaming\nvm

Total Node.js Versions: 3
Active Node.js Version: v12.13.0

IPv6 is enabled. This can slow downloads significantly.

PROBLEMS DETECTED
-----------------
The NVM4W symlink (C:\Program Files\nodejs) was not found in the PATH environment variable.

Find help at https://github.com/coreybutler/nvm-windows/wiki/Common-Issues

Anything else?

I downloaded the source, and noted that in main() the following is run:

    if !isTerminal() {
        alert("NVM for Windows should be run from a terminal such as CMD or PowerShell.", "Terminal Only")
        os.Exit(0)
    }

and that isTerminal() does this:

func isTerminal() bool {
    fileInfo, err := os.Stdout.Stat()
    if err != nil {
        return false
    }
    fmt.Println(fileInfo.Mode())
    return (fileInfo.Mode() & os.ModeCharDevice) != 0
}

I commented the call to alert() and built the binary to test the hypothesis, and as expected the process exits (with 0) and does nothing. Therefore, in the context of TeamCity, stdout is not a character device. That is not entirely unexpected as it is very likely being redirected to a file or some other non character device for capture. That part I can't prove directly.

When alert() runs, it spawns an application modal message box, which must be dismissed which explains the hang. There is no user to click OK.

I propose either a switch to override that isTerminal() behavior, or expand the test to be more permissive:

return fileInfo.Mode()&os.ModeCharDevice == os.ModeCharDevice || fileInfo.Mode()&os.ModeNamedPipe == os.ModeNamedPipe

Something like that. This edit worked for me but it might be nice to incorporate.

RagingTonberry commented 6 months ago

That part I can't prove directly.

Wrote that over a bit of time, this isn't true. I added some debug output to the isTerminal() func that showed that stdout was a named pipe and not a char device - hence the fix that worked for me in the context of TeamCity.

coreybutler commented 6 months ago

The isTerminal feature was added for an obscure use case, but has proven to be more hassle than it is worth. It will be pulled out in the next version (there's already a PR). Unfortunately, it will be a little while until I can release 1.1.13 because our code signing certificate got locked out (slow process to get it reinstated). For the time being, I'd recommend using 1.1.11.

RagingTonberry commented 6 months ago

The isTerminal feature was added for an obscure use case, but has proven to be more hassle than it is worth. It will be pulled out in the next version (there's already a PR). Unfortunately, it will be a little while until I can release 1.1.13 because our code signing certificate got locked out (slow process to get it reinstated). For the time being, I'd recommend using 1.1.11.

Great! Thanks. I'm alright to continue using my compiled version for now.

maoziliang commented 2 months ago

wow. This waste me long time to investigate the reason. 1.1.11 works well with Jenkins agent.