Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/npm/cli.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Karrys <luke@lukekarrys.com>2022-10-19 20:04:29 +0300
committerLuke Karrys <luke@lukekarrys.com>2022-10-19 22:50:41 +0300
commitd3543e945e721783dcb83385935f282a4bb32cf3 (patch)
tree29737357bb3c06fe0804345ee6e7c0e019d3468e
parentbe642c6b8e3df40fd43b0110b30d3ecd44086016 (diff)
feat: output json formatted errors on stdout (#5716)
This also adds a new output method `outputBuffer()` which will buffer all output until it is flushed in the exit handler. This allows the exit handler to catch any errors and append them to the output when in json mode. This was necessary to not introduce a regression in the case of npm/cli#2150. BREAKING CHANGE: `npm` now outputs some json errors on stdout. Previously `npm` would output all json formatted errors on stderr, making it difficult to parse as the stderr stream usually has logs already written to it. In the future, `npm` will differentiate between errors and crashes. Errors, such as `E404` and `ERESOLVE`, will be handled and will continue to be output on stdout. In the case of a crash, `npm` will log the error as usual but will not attempt to display it as json, even in `--json` mode. Moving a case from the category of an error to a crash will not be considered a breaking change. For more information see npm/rfcs#482. Closes #2740 Closes https://github.com/npm/statusboard/issues/589
-rw-r--r--lib/commands/ls.js2
-rw-r--r--lib/npm.js32
-rw-r--r--lib/utils/exit-handler.js8
-rw-r--r--test/fixtures/mock-npm.js9
-rw-r--r--test/lib/utils/exit-handler.js48
5 files changed, 94 insertions, 5 deletions
diff --git a/lib/commands/ls.js b/lib/commands/ls.js
index 6812c3923..7eebdf691 100644
--- a/lib/commands/ls.js
+++ b/lib/commands/ls.js
@@ -177,7 +177,7 @@ class LS extends ArboristWorkspaceCmd {
const [rootError] = tree.errors.filter(e =>
e.code === 'EJSONPARSE' && e.path === resolve(path, 'package.json'))
- this.npm.output(
+ this.npm.outputBuffer(
json
? jsonOutput({ path, problems, result, rootError, seenItems })
: parseable
diff --git a/lib/npm.js b/lib/npm.js
index 763c9b7a3..9fbda9073 100644
--- a/lib/npm.js
+++ b/lib/npm.js
@@ -40,6 +40,7 @@ class Npm extends EventEmitter {
#argvClean = []
#chalk = null
+ #outputBuffer = []
#logFile = new LogFile()
#display = new Display()
#timers = new Timers({
@@ -466,6 +467,37 @@ class Npm extends EventEmitter {
log.showProgress()
}
+ outputBuffer (item) {
+ this.#outputBuffer.push(item)
+ }
+
+ flushOutput (jsonError) {
+ if (!jsonError && !this.#outputBuffer.length) {
+ return
+ }
+
+ if (this.config.get('json')) {
+ const jsonOutput = this.#outputBuffer.reduce((acc, item) => {
+ if (typeof item === 'string') {
+ // try to parse it as json in case its a string
+ try {
+ item = JSON.parse(item)
+ } catch {
+ return acc
+ }
+ }
+ return { ...acc, ...item }
+ }, {})
+ this.output(JSON.stringify({ ...jsonOutput, ...jsonError }, null, 2))
+ } else {
+ for (const item of this.#outputBuffer) {
+ this.output(item)
+ }
+ }
+
+ this.#outputBuffer.length = 0
+ }
+
outputError (...msg) {
log.clearProgress()
// eslint-disable-next-line no-console
diff --git a/lib/utils/exit-handler.js b/lib/utils/exit-handler.js
index 2574a83fc..a9e061de7 100644
--- a/lib/utils/exit-handler.js
+++ b/lib/utils/exit-handler.js
@@ -136,6 +136,7 @@ const exitHandler = err => {
let exitCode
let noLogMessage
+ let jsonError
if (err) {
exitCode = 1
@@ -198,14 +199,13 @@ const exitHandler = err => {
}
if (hasLoadedNpm && npm.config.get('json')) {
- const error = {
+ jsonError = {
error: {
code: err.code,
summary: messageText(summary),
detail: messageText(detail),
},
}
- npm.outputError(JSON.stringify(error, null, 2))
}
if (typeof err.errno === 'number') {
@@ -216,6 +216,10 @@ const exitHandler = err => {
}
}
+ if (hasLoadedNpm) {
+ npm.flushOutput(jsonError)
+ }
+
log.verbose('exit', exitCode || 0)
showLogFileError = (hasLoadedNpm && npm.silent) || noLogMessage
diff --git a/test/fixtures/mock-npm.js b/test/fixtures/mock-npm.js
index 0318eadeb..8a744cd55 100644
--- a/test/fixtures/mock-npm.js
+++ b/test/fixtures/mock-npm.js
@@ -238,6 +238,15 @@ class MockNpm {
}
this._mockOutputs.push(msg)
}
+
+ // with the older fake mock npm there is no
+ // difference between output and outputBuffer
+ // since it just collects the output and never
+ // calls the exit handler, so we just mock the
+ // method the same as output.
+ outputBuffer (...msg) {
+ this.output(...msg)
+ }
}
const FakeMockNpm = (base = {}, t) => {
diff --git a/test/lib/utils/exit-handler.js b/test/lib/utils/exit-handler.js
index 7e3137013..d22ec4dd1 100644
--- a/test/lib/utils/exit-handler.js
+++ b/test/lib/utils/exit-handler.js
@@ -208,7 +208,7 @@ t.test('exit handler called - no npm with error without stack', async (t) => {
})
t.test('console.log output using --json', async (t) => {
- const { exitHandler, outputErrors } = await mockExitHandler(t, {
+ const { exitHandler, outputs } = await mockExitHandler(t, {
config: { json: true },
})
@@ -216,7 +216,7 @@ t.test('console.log output using --json', async (t) => {
t.equal(process.exitCode, 1)
t.same(
- JSON.parse(outputErrors[0]),
+ JSON.parse(outputs[0]),
{
error: {
code: 'EBADTHING', // should default error code to E[A-Z]+
@@ -228,6 +228,50 @@ t.test('console.log output using --json', async (t) => {
)
})
+t.test('merges output buffers errors with --json', async (t) => {
+ const { exitHandler, outputs, npm } = await mockExitHandler(t, {
+ config: { json: true },
+ })
+
+ npm.outputBuffer({ output_data: 1 })
+ npm.outputBuffer(JSON.stringify({ more_data: 2 }))
+ npm.outputBuffer('not json, will be ignored')
+
+ await exitHandler(err('Error: EBADTHING Something happened'))
+
+ t.equal(process.exitCode, 1)
+ t.same(
+ JSON.parse(outputs[0]),
+ {
+ output_data: 1,
+ more_data: 2,
+ error: {
+ code: 'EBADTHING', // should default error code to E[A-Z]+
+ summary: 'Error: EBADTHING Something happened',
+ detail: 'Error: EBADTHING Something happened',
+ },
+ },
+ 'should output expected json output'
+ )
+})
+
+t.test('output buffer without json', async (t) => {
+ const { exitHandler, outputs, npm, logs } = await mockExitHandler(t)
+
+ npm.outputBuffer('output_data')
+ npm.outputBuffer('more_data')
+
+ await exitHandler(err('Error: EBADTHING Something happened'))
+
+ t.equal(process.exitCode, 1)
+ t.same(
+ outputs,
+ [['output_data'], ['more_data']],
+ 'should output expected output'
+ )
+ t.match(logs.error, [['code', 'EBADTHING']])
+})
+
t.test('throw a non-error obj', async (t) => {
const { exitHandler, logs } = await mockExitHandler(t)