Commit 9fa4d33
Changed files (8)
build/core.cjs
@@ -206,7 +206,7 @@ function getErrnoMessage(errno) {
function getExitCodeInfo(exitCode) {
return EXIT_CODES[exitCode];
}
-var formatExitMessage = (code, signal, stderr, from) => {
+var formatExitMessage = (code, signal, stderr, from, details = "") => {
let message = `exit code: ${code}`;
if (code != 0 || signal != null) {
message = `${stderr || "\n"} at ${from}`;
@@ -216,6 +216,11 @@ var formatExitMessage = (code, signal, stderr, from) => {
message += `
signal: ${signal}`;
}
+ if (details) {
+ message += `
+ details:
+${details}`;
+ }
}
return message;
};
@@ -229,8 +234,15 @@ function getCallerLocation(err = new Error("zx error")) {
return getCallerLocationFromString(err.stack);
}
function getCallerLocationFromString(stackString = "unknown") {
- var _a;
- return ((_a = stackString.split(/^\s*(at\s)?/m).filter((s) => s == null ? void 0 : s.includes(":"))[2]) == null ? void 0 : _a.trim()) || stackString;
+ const lines = stackString.split(/^\s*(at\s)?/m).filter((s) => s == null ? void 0 : s.includes(":"));
+ return (lines.find((l) => l.includes("file://")) || lines[3] || // skip getCallerLocation and Proxy.set
+ stackString).trim();
+}
+function findErrors(lines = []) {
+ if (lines.length < 20) return lines.join("\n");
+ let errors = lines.filter((l) => /(fail|error|not ok|exception)/i.test(l));
+ if (errors.length === 0) errors = lines;
+ return errors.slice(0, 20).join("\n") + (errors.length > 20 ? "\n..." : "");
}
// src/core.ts
@@ -853,7 +865,7 @@ var _ProcessOutput = class _ProcessOutput extends Error {
stdall: { get: (0, import_util.once)(() => (0, import_util.bufArrJoin)(dto.store.stdall)) },
message: {
get: (0, import_util.once)(
- () => message || dto.error ? _ProcessOutput.getErrorMessage(dto.error, dto.from) : _ProcessOutput.getExitMessage(dto.code, dto.signal, this.stderr, dto.from)
+ () => message || dto.error ? _ProcessOutput.getErrorMessage(dto.error || new Error(message), dto.from) : _ProcessOutput.getExitMessage(dto.code, dto.signal, this.stderr, dto.from, this.stderr.trim() ? "" : findErrors(this.lines()))
)
}
});
build/core.d.ts
@@ -170,7 +170,7 @@ export declare class ProcessOutput extends Error {
lines(): string[];
valueOf(): string;
[Symbol.iterator](): Iterator<string>;
- static getExitMessage: (code: number | null, signal: NodeJS.Signals | null, stderr: string, from: string) => string;
+ static getExitMessage: (code: number | null, signal: NodeJS.Signals | null, stderr: string, from: string, details?: string) => string;
static getErrorMessage: (err: NodeJS.ErrnoException, from: string) => string;
[inspect.custom](): string;
}
src/core.ts
@@ -27,6 +27,7 @@ import { inspect } from 'node:util'
import { EOL as _EOL } from 'node:os'
import { EventEmitter } from 'node:events'
import {
+ findErrors,
formatErrorMessage,
formatExitMessage,
getCallerLocation,
@@ -733,8 +734,8 @@ export class ProcessOutput extends Error {
stdall: { get: once(() => bufArrJoin(dto.store.stdall)) },
message: { get: once(() =>
message || dto.error
- ? ProcessOutput.getErrorMessage(dto.error, dto.from)
- : ProcessOutput.getExitMessage(dto.code, dto.signal, this.stderr, dto.from)
+ ? ProcessOutput.getErrorMessage(dto.error || new Error(message), dto.from)
+ : ProcessOutput.getExitMessage(dto.code, dto.signal, this.stderr, dto.from, this.stderr.trim() ? '' : findErrors(this.lines()))
),
},
})
src/error.ts
@@ -184,7 +184,8 @@ export const formatExitMessage = (
code: number | null,
signal: NodeJS.Signals | null,
stderr: string,
- from: string
+ from: string,
+ details: string = ''
): string => {
let message = `exit code: ${code}`
if (code != 0 || signal != null) {
@@ -195,6 +196,9 @@ export const formatExitMessage = (
if (signal != null) {
message += `\n signal: ${signal}`
}
+ if (details) {
+ message += `\n details: \n${details}`
+ }
}
return message
@@ -217,10 +221,21 @@ export function getCallerLocation(err = new Error('zx error')): string {
}
export function getCallerLocationFromString(stackString = 'unknown'): string {
+ const lines = stackString
+ .split(/^\s*(at\s)?/m)
+ .filter((s) => s?.includes(':'))
+
return (
+ lines.find((l) => l.includes('file://')) ||
+ lines[3] || // skip getCallerLocation and Proxy.set
stackString
- .split(/^\s*(at\s)?/m)
- .filter((s) => s?.includes(':'))[2]
- ?.trim() || stackString
- )
+ ).trim()
+}
+
+export function findErrors(lines: string[] = []): string {
+ if (lines.length < 20) return lines.join('\n')
+
+ let errors = lines.filter((l) => /(fail|error|not ok|exception)/i.test(l))
+ if (errors.length === 0) errors = lines
+ return errors.slice(0, 20).join('\n') + (errors.length > 20 ? '\n...' : '')
}
test/core.test.js
@@ -203,8 +203,10 @@ describe('core', () => {
err = p
}
assert.ok(err.exitCode > 0)
- assert.ok(err.stderr.includes('wtf: command not found'))
- assert.ok(err[inspect.custom]().includes('Command not found'))
+ assert.match(err.toString(), /command not found/)
+ assert.match(err.valueOf(), /command not found/)
+ assert.match(err.stderr, /wtf: command not found/)
+ assert.match(err[inspect.custom](), /Command not found/)
})
test('error event is handled', async () => {
@@ -1162,11 +1164,30 @@ describe('core', () => {
assert.equal(o.stdout, '')
assert.equal(o.stderr, '')
+ assert.equal(o.stdall, 'foo\n')
assert.equal(o.signal, 'SIGTERM')
assert.equal(o.exitCode, -1)
assert.equal(o.duration, 20)
assert.equal(o.ok, false)
+ assert.equal(
+ o.message,
+ 'msg\n errno: undefined (Unknown error)\n code: undefined\n at '
+ )
assert.equal(Object.prototype.toString.call(o), '[object ProcessOutput]')
+
+ const o1 = new ProcessOutput({
+ code: -1,
+ from: 'file.js(12:34)',
+ store: {
+ stdall: ['error in stdout'],
+ stdout: [],
+ stderr: [],
+ },
+ })
+ assert.equal(
+ o1.message,
+ '\n at file.js(12:34)\n exit code: -1\n details: \nerror in stdout'
+ )
})
test('[Symbol.toPrimitive]', () => {
test/error.test.ts
@@ -21,6 +21,7 @@ import {
getCallerLocationFromString,
formatExitMessage,
formatErrorMessage,
+ findErrors,
} from '../src/error.ts'
describe('error', () => {
@@ -34,8 +35,11 @@ describe('error', () => {
assert.equal(getErrnoMessage(undefined), 'Unknown error')
})
- describe('getCallerLocation()', () => {
- assert.match(getCallerLocation(new Error('Foo')), /Suite\.runInAsyncScope/)
+ test('getCallerLocation()', () => {
+ assert.match(
+ getCallerLocation(new Error('Foo')),
+ /TestContext\.<anonymous>/
+ )
})
describe('getCallerLocationFromString()', () => {
@@ -54,6 +58,7 @@ describe('error', () => {
const stack = `
Error
at getCallerLocation (/Users/user/test.js:22:17)
+ at Proxy.set (/Users/user/test.js:40:10)
at e (/Users/user/test.js:34:13)
at d (/Users/user/test.js:11:5)
at c (/Users/user/test.js:8:5)
@@ -71,6 +76,7 @@ describe('error', () => {
test(`getCallerLocationFromString-JSC`, () => {
const stack = `
getCallerLocation@/Users/user/test.js:22:17
+ Proxy.set@/Users/user/test.js:40:10)
e@/Users/user/test.js:34:13
d@/Users/user/test.js:11:5
c@/Users/user/test.js:8:5
@@ -109,4 +115,21 @@ describe('error', () => {
/Unknown error/
)
})
+
+ test('findErrors()', () => {
+ const lines = [...Array(40).keys()].map((v) => v + '')
+
+ assert.equal(findErrors([]), '', 'empty returns empty')
+ assert.equal(findErrors(['foo', 'bar']), 'foo\nbar', 'squashes a few')
+ assert.equal(
+ findErrors(['failure: foo', 'NOT OK smth', ...lines]),
+ 'failure: foo\nNOT OK smth',
+ 'extracts errors'
+ )
+ assert.equal(
+ findErrors(lines),
+ '0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n...',
+ 'shows a sample'
+ )
+ })
})
test/vendor.test.js
@@ -45,6 +45,7 @@ describe('vendor API', () => {
test('which() available', async () => {
assert.equal(which.sync('npm'), await which('npm'))
+ assert.throws(() => which.sync('not-found-cmd'), /not-found-cmd/)
})
test('minimist available', async () => {
.size-limit.json
@@ -17,14 +17,14 @@
"README.md",
"LICENSE"
],
- "limit": "113.7 kB",
+ "limit": "114.25 kB",
"brotli": false,
"gzip": false
},
{
"name": "js parts",
"path": "build/*.{js,cjs}",
- "limit": "811.6 kB",
+ "limit": "812.15 kB",
"brotli": false,
"gzip": false
},
@@ -45,7 +45,7 @@
{
"name": "all",
"path": ["build/*", "man/*", "README.md", "LICENSE"],
- "limit": "867.5 kB",
+ "limit": "868.0 kB",
"brotli": false,
"gzip": false
}