[BUG] `npm run` erases script output after the final newline
Is there an existing issue for this?
- [x] I have searched the existing issues
This issue exists in the latest npm version
- [x] I am using the latest npm
Current Behavior
Any script which writes to standard output but does not emit a \n as its final character will have output erased when npm run exits.
Expected Behavior
npm run should never interfere with the output of scripts.
Steps To Reproduce
- Create a
package.jsonfile like this:{ "name": "", "version": "0.0.0", "scripts": { "start": "printf 'this output will vanish'; sleep 3" } } - Run
npm run start. - Observe that the output vanishes after three seconds.
Environment
npm -v: 11.6.0node -v: v22.18.0- OS Name: macOS 15.6
- System Model Name: MacBook Pro
- npm config:
//localhost:4873/:_authToken = (protected) //registry.npmjs.org/:_authToken = (protected)
The problem is related to progress output. The behavior does not occur when the --no-progress or --silent options are provided to npm run.
Here are the relevant implementation details of an npm run execution as I've managed to understand them so far:
Npm's constructor instantiates aDisplayobject- The user's script is executed and may produce output
- Something (I haven't tracked this down) emits an
'input'event with the payload'end', which causesDisplay's#inputHandlermethod to be called Display's#inputHandlermethod callsProgress'sresumemethodProgress'sresumemethod calls its#rendermethod without arguments- Since
#renderwas called without arguments,#renderSpinneris immediately called Progress's#renderSpinnerproceeds to call#renderFrame, because#renderingistrueProgress's#renderFramecalls#clearSpinnerProgress's#clearSpinnerdeletes the entire current line
It strikes me as odd that #rendering in Progress is true in this case, as no progress indicator is visible at the time when #renderSpinner is called (but I might be misinterpreting what #rendering is supposed to mean).
Are there any real scenarios where npm run would display a progress indicator after the script finishes?
The input event comes from @npmcli/run-script https://github.com/npm/run-script/blob/b26e154be73f82ecd693b5c4395d3f3da716ca1c/lib/run-script-pkg.js#L63
It was added in https://github.com/npm/run-script/pull/202 and was meant to be a way to tell npm to stop its own output (including the spinner) while userland scripts ran.
Are there any real scenarios where
npm runwould display a progress indicator after the script finishes?
The progress indicator is meant to signal to users that npm is still running and hasn't frozen. If some part of the npm process after npm run takes a while to run, the spinner would show that things haven't frozen. I don't know that the use case there is as important as npm install which is where this is much more likely to happen.
There is no way for npm to know what was displayed during npm run as it does not pass through npm's display layer. I believe at one time an extra newline was output at the end of npm run to mitigate things like this. If we were to re-add this today it would probably want to go in the display layer's #inputHandler.
To add to the complexity here, there is another PR in flight that is on my plate to review and iterate on that solves a problem in the same domain space: https://github.com/npm/cli/pull/8322. There is a race condition when prompting for multiple inputs that turns the spinner back on. I don't think the solutions overlap specifically but the code is likely to intersect at least in some way. That PR already has conflicts w/ latest and will have even more once https://github.com/npm/cli/pull/8633 lands (which is some cleanup based on researching this issue).
at one time an extra newline was output at the end of
npm runto mitigate things like this
The context has mostly left my brain by now so I don't know if this actually makes sense, but while debugging this I recall thinking that maybe a simple "fix" would be for npm to print a newline before the spinner.