CLI ls command: incorrect exit code and use of stdout

(Bryan) #1

What I Wanted to Do

I wanted to run “npm ls --parseable” to list all installed packages in a folder. I have an app that uses this information to present a graphical interface to a user, so this command is being run without direct user interaction (that is, I’m running it programmatically in my app, not typing it into a Terminal.)

I expected this command to return a 0 exit code when it runs correctly.

What Happened Instead

When the command is run in a project that has a missing package or a missing peer dependency, this command returns an exit code of 1. Additionally, the error messages (“missing peer dependency…) are written to BOTH stdout and stderr.

This is not correct. The command should exit with status code 0, because it DID run successfully—it determined which packages are installed. A non-zero status code indicates that a process failed to run.

Additionally, the error messages about missing packages or peer dependencies should be written to stderr only. Adding them to the list of parseable paths that is written to stdout makes parsing that list harder.

The correct approach is to return a 0 status code (the ls command ran successfully) and write any issues about missing packages/deps to stderr ONLY. This way, integrators can check stderr to see if any issues arose, can more reliably parse the output on stdout, and can rely on a correct Unix-standard exit code to decide if the command actually failed, or ran correctly but just found a couple issues.

Reproduction Steps

Run the command above in a project folder with missing packages or peer dependencies.


Platform Info

npm 6.4.1
macOS 10.14.5

$ npm --versions
<!-- paste output here -->
$ node -p process.platform
<!-- paste output here -->
(John Gee) #2

My impression is that npm ls is listing and checking the package dependencies, and the error condition is intended behaviour when it detects issues. However, the documentation does not mention the return status, so I don’t have evidence to back me up!

For interest, I will link to a previous mention of npm ls in the forum where it was explicitly being used to test for missing dependencies using the return status: npm ls requires peer dependencies to be installed

I did not reproduce your report about error messages appearing on both stdout vs stderr:

$ npm ls --parseable >stdout 2>stderr
$ cat stdout
$ cat stderr
npm ERR! missing: commander@2.20.0, required by 7492@1.0.0
npm ERR! peer dep missing: missingPeer@latest, required by 7492@1.0.0
(Bryan) #3

I understand. I would expect the process to exit with 0 because it did, in fact, run correctly. Right now there’s no way to determine if the process truly fails. You have to ask “did it exit with !0 and write nothing to stdout? Guess it failed, then? Or maybe there’s just no packages installed in the project?”

Exiting with 0 to indicate that there were no problems actually running “ls” and then checking stderr to see if the process turned up any issues in the project is far more reliable and straightforward.

If too many people have built things relying on this non-standard behavior, I’ll understand not changing it, but I’d revise it for the next major breaking release.

(Daniel Stockman) #4

In my experience, most of the wonkiness in the npm CLI with regard to stdio and exit codes is due to mitigating Windows anti-patterns (“any messages on stderr == Error?!”, etc). The exit codes tend to be chosen for expressive bash conditionals, e.g, npm ls && echo "yay things are good" || echo "oh no something's wrong", not strictly adhering to arcane unix semantics.

(Bryan) #5

Hmm. I’d expect npm to be a first-class citizen on each platform where it’s supported. If the semantics are different between windows and unix-based systems, npm should behave as each platform expects.

I can handle ls behaving in a non-standard way. The REAL issue is that this has created uncertainty with all other commands. For example, if I run:

npm outdated --json

is there a case where I get a !0 exit code, but also a list of outdated packages on stdout?

Folks implementing npm into other tools have to do a lot of sniffing and guessing because npm doesn’t adhere to the “arcane unix semantics”. The longer I program, the more I realize the folks who came up with arcane standards were pretty bright and settled on those standards for a reason.

(Daniel Stockman) #6

Dude, I completely agree with you, in principle. Check out my comment history in this forum concerning stdio pipes if you doubt me. I’m just relating my understanding (imperfect as it is) of why npm devs over time have made these kinds of trade-offs.

TL;DR: It’s not as cut-and-dried as you make it to be.

1 Like