@jchip/error 2.1.8 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/index.js +18 -12
- package/docs/reference.md +9 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -91,6 +91,7 @@ AggregateError: test
|
|
|
91
91
|
|
|
92
92
|
- Subclass of the built-in [AggregateError]; it does not patch the global
|
|
93
93
|
- `stack` includes the stacks of the aggregated errors, indented
|
|
94
|
+
- Assigning `err.stack` replaces the top part and keeps the aggregated stacks
|
|
94
95
|
- Takes any iterable of errors and the native `{ cause }` option
|
|
95
96
|
|
|
96
97
|
Example with `cleanErrorStack`:
|
package/dist/index.js
CHANGED
|
@@ -14,8 +14,9 @@ export function cleanErrorStack(error, { replacePath = `${process.cwd()}/`, igno
|
|
|
14
14
|
if (!stack) {
|
|
15
15
|
return String(stack);
|
|
16
16
|
}
|
|
17
|
+
// normalize CRLF so every output line ends the same way
|
|
17
18
|
const result = stack
|
|
18
|
-
.split(
|
|
19
|
+
.split(/\r?\n/)
|
|
19
20
|
.map((line) => {
|
|
20
21
|
// keep all non stack tracing lines
|
|
21
22
|
if (!line.match(/ {4,}at/)) {
|
|
@@ -37,10 +38,11 @@ export function cleanErrorStack(error, { replacePath = `${process.cwd()}/`, igno
|
|
|
37
38
|
}
|
|
38
39
|
let path2 = path;
|
|
39
40
|
if (replacePath && replacePath.length > 1) {
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
// strip replacePath only as a leading prefix. An ESM location is a file:// URL, so drop
|
|
42
|
+
// the scheme along with it.
|
|
43
|
+
const prefix = path.startsWith(`file://${replacePath}`) ? `file://${replacePath}` : replacePath;
|
|
44
|
+
if (path.startsWith(prefix))
|
|
45
|
+
path2 = path.slice(prefix.length);
|
|
44
46
|
}
|
|
45
47
|
return `${at}${name}${path2}${tail}`;
|
|
46
48
|
})
|
|
@@ -56,14 +58,13 @@ export function cleanErrorStack(error, { replacePath = `${process.cwd()}/`, igno
|
|
|
56
58
|
* @returns aggregate stack
|
|
57
59
|
*/
|
|
58
60
|
export function aggregateStack(stack, errors) {
|
|
59
|
-
|
|
60
|
-
.
|
|
61
|
-
errors.map &&
|
|
62
|
-
errors.map((e) => {
|
|
61
|
+
const stacks = errors && errors.map
|
|
62
|
+
? errors.map((e) => {
|
|
63
63
|
const s = e && (e.stack || e.message);
|
|
64
64
|
return (s || String(e)).replace(/^/gm, " ");
|
|
65
|
-
})
|
|
66
|
-
|
|
65
|
+
})
|
|
66
|
+
: [];
|
|
67
|
+
return [stack].concat(stacks).join("\n");
|
|
67
68
|
}
|
|
68
69
|
/**
|
|
69
70
|
* build the aggregate stack of an AggregateError
|
|
@@ -102,13 +103,18 @@ export class AggregateError extends globalThis.AggregateError {
|
|
|
102
103
|
// save original stack
|
|
103
104
|
__stack: {
|
|
104
105
|
enumerable: false,
|
|
106
|
+
writable: true,
|
|
105
107
|
value: this.stack,
|
|
106
108
|
},
|
|
107
|
-
// make aggregate stack
|
|
109
|
+
// make aggregate stack. Writing it replaces the header and keeps the errors' stacks.
|
|
108
110
|
stack: {
|
|
109
111
|
get() {
|
|
110
112
|
return aggStack || (aggStack = aggregateErrorStack(this));
|
|
111
113
|
},
|
|
114
|
+
set(value) {
|
|
115
|
+
this.__stack = value;
|
|
116
|
+
aggStack = "";
|
|
117
|
+
},
|
|
112
118
|
},
|
|
113
119
|
});
|
|
114
120
|
}
|
package/docs/reference.md
CHANGED
|
@@ -33,7 +33,7 @@ Returns the text of `error.stack` with noisy stack frames removed and file paths
|
|
|
33
33
|
|
|
34
34
|
| option | type | default | behavior |
|
|
35
35
|
| --- | --- | --- | --- |
|
|
36
|
-
| `replacePath` | `false \| string` | `` `${process.cwd()}/` `` (evaluated on each call) |
|
|
36
|
+
| `replacePath` | `false \| string` | `` `${process.cwd()}/` `` (evaluated on each call) | Leading prefix removed from each kept frame path. Ignored when `false`, empty, or exactly 1 character long (so `"/"` does nothing). |
|
|
37
37
|
| `ignorePathFilter` | `(string \| RegExp)[]` | `[]` | Extra filters. A frame whose path matches any filter is dropped. Added to the built-in filter. |
|
|
38
38
|
|
|
39
39
|
Input selection:
|
|
@@ -42,16 +42,16 @@ Input selection:
|
|
|
42
42
|
- When neither exists (or `error` is `null`/`undefined`), nothing is cleaned and the return value is `String(error && (error.stack || error.message))`. That gives `"null"` for `null` and `"undefined"` for `undefined`, `{}`, or an object with empty stack and no message.
|
|
43
43
|
- A non-string, truthy `stack` (such as a number) throws `TypeError` (`stack.split is not a function`).
|
|
44
44
|
|
|
45
|
-
Processing, applied to each line of the text split on `"\n"`:
|
|
45
|
+
Processing, applied to each line of the text split on `"\n"` or `"\r\n"`:
|
|
46
46
|
|
|
47
47
|
1. A line that does not match `/ {4,}at/` (an `at` preceded by 4 or more spaces anywhere in the line) is kept unchanged. This covers the message line, `Require stack:` lines, and frames indented with fewer than 4 spaces.
|
|
48
48
|
2. A frame line is parsed with `/( {4,}at )(?:([^(]*\()(.+)(\).*)|(.+))/`. It has two forms. `at name (location)` takes the location from the first `(` to the last `)`, so a `)` inside a path does not cut it short. `at location` has no parentheses. Node uses it for anonymous functions and ES module top-level code.
|
|
49
49
|
3. The frame is dropped when parsing fails (such as a bare ` at`), or unless the location contains `scheme://` (regex `/[^:]+:\/\//`) or `path.isAbsolute(location)` is true. That removes `node:internal/...` frames in either form, `(<anonymous>)`, `(index 0)`, and relative paths. `path.isAbsolute` is platform specific, so a Windows path like `C:\a\b.js` is dropped on POSIX.
|
|
50
50
|
4. Backslashes in the location are replaced with `/`.
|
|
51
51
|
5. The frame is dropped if the location matches the built-in filter or any `ignorePathFilter` entry. A `RegExp` entry uses `location.match(re)`. A string entry uses `location.includes(str)`. Falsy entries (such as `""`) are skipped.
|
|
52
|
-
6. Otherwise `replacePath` (when usable) is removed. A location that starts with `file://` + `replacePath` loses both, so an ES module URL under the current directory becomes a relative path. Any other location
|
|
52
|
+
6. Otherwise `replacePath` (when usable) is removed. A location that starts with `file://` + `replacePath` loses both, so an ES module URL under the current directory becomes a relative path. Any other location loses `replacePath` only when it starts with it (plain string, not a regex). A match in the middle of a path is left alone, and so is a `file://` URL outside `replacePath`.
|
|
53
53
|
7. The kept frame is rebuilt as `at` + name + new location + the text after the last `)`. Text before the 4-space indent is not kept.
|
|
54
|
-
8. Dropped frames are removed and the remaining lines are joined with `"\n"`. Blank lines in the original text are kept.
|
|
54
|
+
8. Dropped frames are removed and the remaining lines are joined with `"\n"`. Blank lines in the original text are kept. A CRLF stack therefore comes back with LF line endings throughout.
|
|
55
55
|
|
|
56
56
|
Real stacks from CommonJS and ES modules are covered by tests on Node 22, 24 and 26 (`test/spec/real-stack.spec.ts`).
|
|
57
57
|
|
|
@@ -87,8 +87,8 @@ Nested aggregate stacks indent again, so each nesting level adds 2 spaces.
|
|
|
87
87
|
|
|
88
88
|
Edge cases on `errors`:
|
|
89
89
|
|
|
90
|
-
- An array (or any object with a `map` function) is mapped. `[]` returns `stack` alone
|
|
91
|
-
- A falsy `errors` (`null`, `undefined`, `""`) or a value with no `map` function (such as a string or a plain object without `map`) is treated as no errors,
|
|
90
|
+
- An array (or any object with a `map` function) is mapped. `[]` returns `stack` alone.
|
|
91
|
+
- A falsy `errors` (`null`, `undefined`, `""`) or a value with no `map` function (such as a string or a plain object without `map`) is treated as no errors, and also returns `stack` alone, for example `aggregateStack("t", null) === "t"`.
|
|
92
92
|
- A truthy `errors.map` that is not a function throws `TypeError`.
|
|
93
93
|
- `stack` is not checked. A non-string is joined as is.
|
|
94
94
|
|
|
@@ -141,13 +141,13 @@ Instance properties (all defined with `Object.defineProperty`, so `Object.keys(e
|
|
|
141
141
|
| --- | --- | --- |
|
|
142
142
|
| `name` | non-enumerable, non-writable, non-configurable own property | `"AggregateError"`. A subclass cannot override it by assignment. |
|
|
143
143
|
| `errors` | non-enumerable, writable, configurable | `Array.from(errors)`: a new array, even when `errors` is an array. |
|
|
144
|
-
| `__stack` | non-enumerable,
|
|
145
|
-
| `stack` | non-enumerable, configurable accessor | Getter returning `aggregateErrorStack(this)`, computed on first read and cached. |
|
|
144
|
+
| `__stack` | non-enumerable, writable, non-configurable | `this.stack` read right after `super()`: the native stack text of the error. Writing `stack` replaces it. |
|
|
145
|
+
| `stack` | non-enumerable, configurable accessor | Getter returning `aggregateErrorStack(this)`, computed on first read and cached. The setter replaces the header. |
|
|
146
146
|
|
|
147
147
|
`stack` details:
|
|
148
148
|
|
|
149
149
|
- The cache means `errors` changes made after the first read of `stack` are not reflected. Changes made before the first read are.
|
|
150
|
-
-
|
|
150
|
+
- Assigning `err.stack = text` replaces the header: `__stack` becomes `text` and the cache is cleared. The next read gives `text` followed by the errors' indented stacks, so the aggregated stacks are kept.
|
|
151
151
|
- `instanceof` works against the native class (`new AggregateError([]) instanceof globalThis.AggregateError` is `true`). A native instance is not an instance of this class.
|
|
152
152
|
- `console.log(err)` and `err.stack` in a debugger show the aggregate form. `cleanErrorStack(err)` therefore also cleans the nested error stacks, and the nested lines keep their 2-space indent.
|
|
153
153
|
|