Skip to content

Commit 53c26e5

Browse files
authored
Merge pull request #830 from adobe/fix-oclif-error-handling
fix: bin/run error handling path for oclif v4
2 parents 0c2f3f8 + 60ecc16 commit 53c26e5

2 files changed

Lines changed: 41 additions & 2 deletions

File tree

bin/run

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@ governing permissions and limitations under the License.
1919
process.stderr.setMaxListeners(30)
2020
process.stdout.setMaxListeners(30)
2121

22+
// `@oclif/core` v4 exposes `handle` and `flush` as named exports on their
23+
// respective subpath modules. Passing the module object (instead of the
24+
// function) to `Promise#catch` / `Promise#then` silently drops the
25+
// handler: errors escape to Node's default uncaught-exception printer
26+
// instead of oclif's renderer, and `flush()` is never invoked.
27+
// Destructure the functions explicitly so both paths run as intended.
28+
const { handle } = require('@oclif/core/handle')
29+
const { flush } = require('@oclif/core/flush')
30+
2231
require('../src/').run()
23-
.then(() => require('@oclif/core/flush'))
24-
.catch(require('@oclif/core/handle'))
32+
.then(flush)
33+
.catch(handle)

e2e/e2e.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,45 @@ governing permissions and limitations under the License.
1010
*/
1111

1212
const execa = require('execa')
13+
const path = require('path')
1314
const fs = jest.requireActual('fs')
1415
const util = require('util')
1516
const fse = {
1617
mkdir: util.promisify(fs.mkdir),
1718
rm: util.promisify(fs.rm)
1819
}
1920

21+
const BIN_RUN = path.resolve(__dirname, '../bin/run')
22+
2023
jest.setTimeout(120000)
2124

25+
test('errors render through oclif, not Node\'s uncaught-exception printer', async () => {
26+
// Spawns the real bin/run with an argv guaranteed to trigger a flag
27+
// parse error from `@oclif/core`. When the bin's `.catch` handler is
28+
// wired correctly, oclif's renderer produces a clean single-line
29+
// `Error: …` message. When the handler is dropped (regression
30+
// tracked in adobe/aio-cli#829 / ACNA-4659), the rejection escapes
31+
// to Node and the output instead contains a source frame, a caret,
32+
// a stack trace, and a `Node.js vXX` footer. Asserting on the
33+
// absence of those Node-specific markers detects the regression
34+
// without coupling to oclif's exact prefix.
35+
const result = await execa('node', [BIN_RUN, 'app', 'use', '--no-such-flag'],
36+
{ reject: false })
37+
38+
// Non-zero exit on a parse error is part of the contract.
39+
expect(result.exitCode).not.toBe(0)
40+
41+
// The user-facing message must be present so the customer knows what
42+
// went wrong; the framework renders it on stderr.
43+
expect(result.stderr).toMatch(/Nonexistent flag.*--no-such-flag/)
44+
45+
// None of these substrings should appear: each is a fingerprint of
46+
// Node's default unhandled-rejection / uncaught-exception output.
47+
expect(result.stderr).not.toMatch(/at processTicksAndRejections/)
48+
expect(result.stderr).not.toMatch(/^Node\.js v\d+/m)
49+
expect(result.stderr).not.toMatch(/^\s+\^\s*$/m)
50+
})
51+
2252
test('cli init test', async () => {
2353
const testFolder = 'e2e_test_run'
2454

0 commit comments

Comments
 (0)