@cjser/clean-stack 6.0.0-cjser.2 → 7.0.0-cjser.2
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/dist-cjser/index.cjs +80 -30
- package/index.d.ts +16 -6
- package/index.js +158 -45
- package/package.json +9 -14
- package/readme.md +17 -4
package/dist-cjser/index.cjs
CHANGED
|
@@ -26,7 +26,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
26
26
|
));
|
|
27
27
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
28
|
|
|
29
|
-
// packages/@cjser/clean-stack/index.js
|
|
29
|
+
// packages/@cjser/clean-stack.tmp-24-1793723115467/index.js
|
|
30
30
|
var index_exports = {};
|
|
31
31
|
__export(index_exports, {
|
|
32
32
|
default: () => cleanStack
|
|
@@ -34,52 +34,102 @@ __export(index_exports, {
|
|
|
34
34
|
module.exports = __toCommonJS(index_exports);
|
|
35
35
|
var import_escape_string_regexp = __toESM(require("@cjser/escape-string-regexp"), 1);
|
|
36
36
|
var import_url_extras = require("@cjser/url-extras");
|
|
37
|
-
var
|
|
38
|
-
var pathRegex =
|
|
39
|
-
var
|
|
37
|
+
var frameStartRegex = new RegExp("^\\s+at\\b", "v");
|
|
38
|
+
var pathRegex = new RegExp("^(?:(?:node|node:[\\w\\-\\/]+|internal\\/[\\w\\/]+(?<!\\/)|\\w+)(?:\\.js)?:\\d+:\\d+|native$)", "v");
|
|
39
|
+
var asyncPrefixRegex = new RegExp("^(?:async|new) ", "v");
|
|
40
|
+
var bareFrameEndRegex = new RegExp(":(?:\\d+:\\d+|0x[\\da-f]+)$", "v");
|
|
41
|
+
var simplePathRegex = new RegExp("^\\w+\\.js:\\d+:\\d+$", "v");
|
|
42
|
+
function getFrame(line) {
|
|
43
|
+
const frameStart = frameStartRegex.exec(line);
|
|
44
|
+
if (!frameStart) {
|
|
45
|
+
return void 0;
|
|
46
|
+
}
|
|
47
|
+
const end = line.trimEnd().length;
|
|
48
|
+
if (line[end - 1] === ")") {
|
|
49
|
+
let depth = 0;
|
|
50
|
+
let pathStart = -1;
|
|
51
|
+
for (let index = frameStart[0].length; index < end; index++) {
|
|
52
|
+
const character = line[index];
|
|
53
|
+
if (character === "(") {
|
|
54
|
+
if (depth === 0) {
|
|
55
|
+
pathStart = index + 1;
|
|
56
|
+
}
|
|
57
|
+
depth++;
|
|
58
|
+
} else if (character === ")" && depth > 0) {
|
|
59
|
+
depth--;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (pathStart !== -1) {
|
|
63
|
+
const path2 = line.slice(pathStart, end - 1);
|
|
64
|
+
return path2 === "" ? void 0 : { path: path2, pathIndex: pathStart, hasFunctionName: true };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const rest = line.slice(frameStart[0].length, end).trimStart();
|
|
68
|
+
const path = asyncPrefixRegex.test(rest) ? rest.slice(rest.indexOf(" ") + 1) : rest;
|
|
69
|
+
return path === "native" || bareFrameEndRegex.test(path) ? { path, pathIndex: end - path.length, hasFunctionName: false } : void 0;
|
|
70
|
+
}
|
|
40
71
|
function cleanStack(stack, { pretty = false, basePath, pathFilter } = {}) {
|
|
41
|
-
var _a;
|
|
72
|
+
var _a, _b;
|
|
42
73
|
if (typeof stack !== "string") {
|
|
43
74
|
return void 0;
|
|
44
75
|
}
|
|
45
|
-
const
|
|
76
|
+
const slashedBasePath = basePath == null ? void 0 : basePath.replaceAll("\\", "/");
|
|
77
|
+
const isAbsoluteBasePath = Boolean(slashedBasePath == null ? void 0 : slashedBasePath.startsWith("/"));
|
|
78
|
+
const isWindowsDrivePath = new RegExp("^[A-Za-z]:", "v").test(slashedBasePath ?? "");
|
|
79
|
+
const normalizedBasePath = slashedBasePath == null ? void 0 : slashedBasePath.split("/").filter((segment) => segment !== "").join("/");
|
|
80
|
+
let basePathPrefix = "";
|
|
81
|
+
if (isAbsoluteBasePath) {
|
|
82
|
+
basePathPrefix = "(?:file:///?|/{1,2})";
|
|
83
|
+
} else if (isWindowsDrivePath) {
|
|
84
|
+
basePathPrefix = "(?:file:///)?";
|
|
85
|
+
}
|
|
86
|
+
const basePathBoundary = normalizedBasePath === "" ? "" : "(?:/|$)";
|
|
87
|
+
const basePathRegex = basePath && new RegExp(`^${basePathPrefix}${(0, import_escape_string_regexp.default)(normalizedBasePath)}${basePathBoundary}`, "gv");
|
|
46
88
|
let homeDirectory = "";
|
|
47
89
|
if (pretty) {
|
|
48
|
-
const os = (_a = process.getBuiltinModule) == null ? void 0 :
|
|
90
|
+
const os = (_b = (_a = globalThis.process) == null ? void 0 : _a.getBuiltinModule) == null ? void 0 : _b.call(_a, "node:os");
|
|
49
91
|
homeDirectory = os ? os.homedir().replaceAll("\\", "/") : "";
|
|
92
|
+
while (homeDirectory.length > 1 && homeDirectory.endsWith("/")) {
|
|
93
|
+
homeDirectory = homeDirectory.slice(0, -1);
|
|
94
|
+
}
|
|
50
95
|
}
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
96
|
+
const lines = stack.split("\n").map((rawLine) => {
|
|
97
|
+
const frame = getFrame(rawLine);
|
|
98
|
+
return {
|
|
99
|
+
line: frame ? rawLine.replaceAll("\\", "/") : rawLine,
|
|
100
|
+
path: frame == null ? void 0 : frame.path.replaceAll("\\", "/"),
|
|
101
|
+
pathIndex: frame == null ? void 0 : frame.pathIndex,
|
|
102
|
+
hasFunctionName: frame == null ? void 0 : frame.hasFunctionName
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
return lines.filter(({ path, hasFunctionName }) => {
|
|
106
|
+
if (path === void 0) {
|
|
54
107
|
return true;
|
|
55
108
|
}
|
|
56
|
-
const
|
|
57
|
-
if (
|
|
109
|
+
const isInternalPath = pathRegex.test(path) && (hasFunctionName || !simplePathRegex.test(path));
|
|
110
|
+
if (isInternalPath || path.includes(".app/Contents/Resources/electron.asar") || path.includes(".app/Contents/Resources/default_app.asar") || path.includes("node_modules/electron/dist/resources/electron.asar") || path.includes("node_modules/electron/dist/resources/default_app.asar") || path.includes("node_modules/babel-polyfill/") || path.includes("node_modules/pirates/")) {
|
|
58
111
|
return false;
|
|
59
112
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return false;
|
|
113
|
+
return pathFilter ? pathFilter(path) : true;
|
|
114
|
+
}).filter(({ line }) => line.trim() !== "").map(({ line, path, pathIndex }) => {
|
|
115
|
+
if (path === void 0 || !basePathRegex && !pretty) {
|
|
116
|
+
return line;
|
|
65
117
|
}
|
|
66
|
-
|
|
67
|
-
}).filter((line) => line.trim() !== "").map((line) => {
|
|
118
|
+
let newPath = path;
|
|
68
119
|
if (basePathRegex) {
|
|
69
|
-
|
|
120
|
+
newPath = newPath.replaceAll(basePathRegex, "");
|
|
70
121
|
}
|
|
71
122
|
if (pretty) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
if (homeDirectory) {
|
|
78
|
-
filePath = filePath.replace(homeDirectory, "~");
|
|
123
|
+
if (newPath.startsWith("file://") && !new RegExp("[#?]|%0[ad]", "iv").test(newPath)) {
|
|
124
|
+
try {
|
|
125
|
+
newPath = (0, import_url_extras.fileURLToPath)(newPath).replaceAll("\\", "/");
|
|
126
|
+
} catch {
|
|
79
127
|
}
|
|
80
|
-
|
|
81
|
-
|
|
128
|
+
}
|
|
129
|
+
if (homeDirectory && (newPath === homeDirectory || newPath.startsWith(homeDirectory + "/"))) {
|
|
130
|
+
newPath = "~" + newPath.slice(homeDirectory.length);
|
|
131
|
+
}
|
|
82
132
|
}
|
|
83
|
-
return line;
|
|
133
|
+
return line.slice(0, pathIndex) + newPath + line.slice(pathIndex + path.length);
|
|
84
134
|
}).join("\n");
|
|
85
135
|
}
|
package/index.d.ts
CHANGED
|
@@ -5,23 +5,33 @@ export type Options = {
|
|
|
5
5
|
- `/Users/sindresorhus/dev/clean-stack/unicorn.js:2:15` → `~/dev/clean-stack/unicorn.js:2:15`
|
|
6
6
|
- `file:///Users/sindresorhus/dev/clean-stack/unicorn.js:2:15` → `~/dev/clean-stack/unicorn.js:2:15`
|
|
7
7
|
|
|
8
|
-
When enabled, file URLs are converted to regular paths for better readability and IDE integration.
|
|
8
|
+
When enabled, file URLs are converted to regular paths for better readability and IDE integration. URLs that cannot be converted, contain `?` or `#`, or decode to a line break are left unchanged.
|
|
9
|
+
|
|
10
|
+
`pretty` and `basePath` only rewrite frame paths. Backslashes become `/` throughout each frame. Error message text is preserved except for blank lines; lines shaped like frames are treated as frames.
|
|
9
11
|
|
|
10
12
|
@default false
|
|
11
13
|
*/
|
|
12
14
|
readonly pretty?: boolean;
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
|
-
Remove the given base path from stack trace file paths, effectively turning absolute paths into relative ones.
|
|
17
|
+
Remove the given base path from stack trace file paths, effectively turning absolute paths into relative ones. It will also transform absolute file URLs into relative paths.
|
|
18
|
+
|
|
19
|
+
Supports absolute or relative paths, either separator, and the filesystem root. A leading slash only matches absolute paths. Relative base paths do not match inside `file:///`, except Windows drive paths such as `C:/Users`.
|
|
16
20
|
|
|
17
|
-
Example with `'/Users/sindresorhus/dev/clean-stack
|
|
21
|
+
Example with `'/Users/sindresorhus/dev/clean-stack'` as `basePath`:
|
|
18
22
|
|
|
19
23
|
`/Users/sindresorhus/dev/clean-stack/unicorn.js:2:15` → `unicorn.js:2:15`
|
|
24
|
+
|
|
25
|
+
`basePath` matches before file URLs are decoded, so `/Users/John Doe/dev` does not match `file:///Users/John%20Doe/dev`. After a match, remaining URL escapes stay encoded, even with `pretty`.
|
|
26
|
+
|
|
27
|
+
Frames like `at fn (unicorn.js:2:15)` are removed because they resemble old internal Node.js frames. Cleaning again after `basePath` produces this form can therefore drop frames for files directly in the base path. Paths with a directory, like `at fn (src/unicorn.js:2:15)`, and bare frames like `at unicorn.js:2:15` are unaffected.
|
|
20
28
|
*/
|
|
21
29
|
readonly basePath?: string;
|
|
22
30
|
|
|
23
31
|
/**
|
|
24
|
-
Remove
|
|
32
|
+
Remove stack lines where the callback returns `false`. It receives the path without the closing parenthesis, with `/` separators, before `basePath` or `pretty` is applied. A pathless frame such as `at async Promise.all (index 0)` yields `index 0`.
|
|
33
|
+
|
|
34
|
+
Balanced parentheses in function names, such as `at render (partial) (/base/a.js:6:9)`, and paths, such as `C:/Program Files (x86)/app.js:1:1`, are supported. Unbalanced parentheses in a function name, such as `at a(b (/base/a.js:6:9)`, can cause part of the name to be included in the path.
|
|
25
35
|
|
|
26
36
|
@example
|
|
27
37
|
```
|
|
@@ -47,8 +57,8 @@ export type Options = {
|
|
|
47
57
|
/**
|
|
48
58
|
Clean up error stack traces. Removes the mostly unhelpful internal Node.js entries.
|
|
49
59
|
|
|
50
|
-
@param stack - The `stack` property of an `Error`.
|
|
51
|
-
@returns The cleaned stack or `undefined` if the given `stack` is
|
|
60
|
+
@param stack - The `stack` property of an `Error`, or `undefined`.
|
|
61
|
+
@returns The cleaned stack or `undefined` if the given `stack` is not a string.
|
|
52
62
|
|
|
53
63
|
@example
|
|
54
64
|
```
|
package/index.js
CHANGED
|
@@ -1,81 +1,194 @@
|
|
|
1
1
|
import escapeStringRegexp from '@cjser/escape-string-regexp';
|
|
2
2
|
import {fileURLToPath} from '@cjser/url-extras';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
|
|
4
|
+
// A frame is indented, so a line that starts with `at` in the first column is text.
|
|
5
|
+
const frameStartRegex = /^\s+at\b/v;
|
|
6
|
+
// A `node:` module name is not limited to word characters, as names such as
|
|
7
|
+
// `node:internal/fs/cp/cp-sync` and `node:internal/tls/secure-context` contain a dash. A
|
|
8
|
+
// bare `internal/` path keeps its old shape, so that a project of its own keeps at
|
|
9
|
+
// least the frames whose name holds a dash.
|
|
10
|
+
const pathRegex = /^(?:(?:node|node:[\w\-\/]+|internal\/[\w\/]+(?<!\/)|\w+)(?:\.js)?:\d+:\d+|native$)/v;
|
|
11
|
+
// The only prefixes V8 puts on a frame that has no function name.
|
|
12
|
+
const asyncPrefixRegex = /^(?:async|new) /v;
|
|
13
|
+
// A bare frame has no parentheses to end it, so it is recognised by the location it ends
|
|
14
|
+
// in: `:line:column` for a file, as in `at /a/b.js:1:2`, or `:0x…` for the offset of a
|
|
15
|
+
// WebAssembly function. The colon is part of that, so `/a/0xdeadbeef` is text, and it
|
|
16
|
+
// keeps the scan to the colons of the line.
|
|
17
|
+
const bareFrameEndRegex = /:(?:\d+:\d+|0x[\da-f]+)$/v;
|
|
18
|
+
const simplePathRegex = /^\w+\.js:\d+:\d+$/v;
|
|
19
|
+
|
|
20
|
+
// A stack frame is `at <function name> (<path>)`, or just `at <path>` when there is no
|
|
21
|
+
// function name. A line has to start with `at` for either form, so an error message that
|
|
22
|
+
// merely holds ` at ` is never mistaken for a frame, which is what `frameStartRegex` is
|
|
23
|
+
// for. A frame also has to end the way a frame ends, so that an indented line of a
|
|
24
|
+
// multi-line error message, such as ` at least one plugin is broken`, is text and can
|
|
25
|
+
// never be handed to `pathFilter`.
|
|
26
|
+
//
|
|
27
|
+
// The path is found by index and not by a regex, as a path can hold both a space and a
|
|
28
|
+
// parenthesis, as in `C:/Program Files (x86)/app.js:1:1`, which a regex has to
|
|
29
|
+
// backtrack over. `pathIndex` is where the path starts in the line, and
|
|
30
|
+
// `hasFunctionName` tells a bare simple relative path, which may be the user's own, from
|
|
31
|
+
// the same name inside `at fn (…)`, which is an old internal Node.js frame.
|
|
32
|
+
function getFrame(line) {
|
|
33
|
+
const frameStart = frameStartRegex.exec(line);
|
|
34
|
+
if (!frameStart) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Trailing whitespace is part of neither form of the path, and a CRLF line keeps
|
|
39
|
+
// its `\r`.
|
|
40
|
+
const end = line.trimEnd().length;
|
|
41
|
+
|
|
42
|
+
// `at fn (/a/b.js:1:2)` — the path is what sits inside the parenthesis the frame ends
|
|
43
|
+
// with. A function name can hold a balanced parenthesis of its own, as V8 prints a
|
|
44
|
+
// computed key verbatim, and a path can hold one too, as in
|
|
45
|
+
// `C:/Program Files (x86)/app.js:1:1` or `paren)dir/app.js`, so the one to take is the
|
|
46
|
+
// last that is not inside another.
|
|
47
|
+
if (line[end - 1] === ')') {
|
|
48
|
+
let depth = 0;
|
|
49
|
+
let pathStart = -1;
|
|
50
|
+
for (let index = frameStart[0].length; index < end; index++) {
|
|
51
|
+
const character = line[index];
|
|
52
|
+
if (character === '(') {
|
|
53
|
+
if (depth === 0) {
|
|
54
|
+
pathStart = index + 1;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
depth++;
|
|
58
|
+
} else if (character === ')' && depth > 0) {
|
|
59
|
+
depth--;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (pathStart !== -1) {
|
|
64
|
+
const path = line.slice(pathStart, end - 1);
|
|
65
|
+
return path === '' ? undefined : {path, pathIndex: pathStart, hasFunctionName: true};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// `at /a/b.js:1:2`, and the `async` or `new` form of it. Everything after `at` is
|
|
70
|
+
// the path, so that a path holding a space survives. `at native` is the one bare
|
|
71
|
+
// frame without a line and column.
|
|
72
|
+
const rest = line.slice(frameStart[0].length, end).trimStart();
|
|
73
|
+
const path = asyncPrefixRegex.test(rest) ? rest.slice(rest.indexOf(' ') + 1) : rest;
|
|
74
|
+
// The path is the tail of the line, so `pathIndex` holds for the trimmed and the
|
|
75
|
+
// `async`/`new` form too.
|
|
76
|
+
return path === 'native' || bareFrameEndRegex.test(path)
|
|
77
|
+
? {path, pathIndex: end - path.length, hasFunctionName: false}
|
|
78
|
+
: undefined;
|
|
79
|
+
}
|
|
7
80
|
|
|
8
81
|
export default function cleanStack(stack, {pretty = false, basePath, pathFilter} = {}) {
|
|
9
82
|
if (typeof stack !== 'string') {
|
|
10
83
|
return undefined;
|
|
11
84
|
}
|
|
12
85
|
|
|
13
|
-
|
|
86
|
+
// Windows separators become `/`, and empty segments are dropped so that `/dev`,
|
|
87
|
+
// `//server/dev`, and `/dev/` name the same base path. A base path that started with
|
|
88
|
+
// a slash only ever matches an absolute path, as `dev/a.js` is not below `/dev`.
|
|
89
|
+
const slashedBasePath = basePath?.replaceAll('\\', '/');
|
|
90
|
+
const isAbsoluteBasePath = Boolean(slashedBasePath?.startsWith('/'));
|
|
91
|
+
const isWindowsDrivePath = /^[A-Za-z]:/v.test(slashedBasePath ?? '');
|
|
92
|
+
const normalizedBasePath = slashedBasePath?.split('/').filter(segment => segment !== '').join('/');
|
|
93
|
+
|
|
94
|
+
// The base path must be the *whole* leading part of a path, otherwise
|
|
95
|
+
// `/Users/foo/dev` would truncate `/Users/foo/development` into `elopment`. A file
|
|
96
|
+
// URL carries its own leading slash, so `file:///dev` matches as `/dev` does. A
|
|
97
|
+
// Windows drive path is absolute in practice, and a relative base path is not, so
|
|
98
|
+
// only the former may reach behind `file:///`.
|
|
99
|
+
let basePathPrefix = '';
|
|
100
|
+
if (isAbsoluteBasePath) {
|
|
101
|
+
basePathPrefix = '(?:file:///?|/{1,2})';
|
|
102
|
+
} else if (isWindowsDrivePath) {
|
|
103
|
+
basePathPrefix = '(?:file:///)?';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const basePathBoundary = normalizedBasePath === '' ? '' : '(?:/|$)';
|
|
107
|
+
const basePathRegex = basePath && new RegExp(`^${basePathPrefix}${escapeStringRegexp(normalizedBasePath)}${basePathBoundary}`, 'gv');
|
|
14
108
|
|
|
15
109
|
let homeDirectory = '';
|
|
16
110
|
if (pretty) {
|
|
17
|
-
const os = process
|
|
111
|
+
const os = globalThis.process?.getBuiltinModule?.('node:os');
|
|
18
112
|
homeDirectory = os ? os.homedir().replaceAll('\\', '/') : '';
|
|
113
|
+
// `HOME` can end with separators, which the comparison below would double.
|
|
114
|
+
while (homeDirectory.length > 1 && homeDirectory.endsWith('/')) {
|
|
115
|
+
homeDirectory = homeDirectory.slice(0, -1);
|
|
116
|
+
}
|
|
19
117
|
}
|
|
20
118
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
119
|
+
const lines = stack.split('\n').map(rawLine => {
|
|
120
|
+
const frame = getFrame(rawLine);
|
|
121
|
+
|
|
122
|
+
// Windows separators become `/`, but only in a stack frame. An error message can
|
|
123
|
+
// hold a path of its own, and that text must survive untouched. The separators
|
|
124
|
+
// do not change the length of the line, so `pathIndex` stays valid.
|
|
125
|
+
return {
|
|
126
|
+
line: frame ? rawLine.replaceAll('\\', '/') : rawLine,
|
|
127
|
+
path: frame?.path.replaceAll('\\', '/'),
|
|
128
|
+
pathIndex: frame?.pathIndex,
|
|
129
|
+
hasFunctionName: frame?.hasFunctionName,
|
|
130
|
+
};
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
return lines
|
|
134
|
+
.filter(({path, hasFunctionName}) => {
|
|
135
|
+
if (path === undefined) {
|
|
26
136
|
return true; // Keep lines without paths (like error messages)
|
|
27
137
|
}
|
|
28
138
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
//
|
|
139
|
+
// Filter out Electron internal paths, and internal Node.js paths. A bare simple
|
|
140
|
+
// relative path like `foo.js:1:1` matches the Node.js pattern, so it is
|
|
141
|
+
// exempted from that part only. The user's `pathFilter` still applies to it.
|
|
142
|
+
const isInternalPath = pathRegex.test(path) && (hasFunctionName || !simplePathRegex.test(path));
|
|
32
143
|
if (
|
|
33
|
-
|
|
34
|
-
||
|
|
35
|
-
||
|
|
36
|
-
||
|
|
144
|
+
isInternalPath
|
|
145
|
+
|| path.includes('.app/Contents/Resources/electron.asar')
|
|
146
|
+
|| path.includes('.app/Contents/Resources/default_app.asar')
|
|
147
|
+
|| path.includes('node_modules/electron/dist/resources/electron.asar')
|
|
148
|
+
|| path.includes('node_modules/electron/dist/resources/default_app.asar')
|
|
149
|
+
|| path.includes('node_modules/babel-polyfill/')
|
|
150
|
+
|| path.includes('node_modules/pirates/')
|
|
37
151
|
) {
|
|
38
152
|
return false;
|
|
39
153
|
}
|
|
40
154
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
155
|
+
return pathFilter ? pathFilter(path) : true;
|
|
156
|
+
})
|
|
157
|
+
.filter(({line}) => line.trim() !== '')
|
|
158
|
+
.map(({line, path, pathIndex}) => {
|
|
159
|
+
if (path === undefined || (!basePathRegex && !pretty)) {
|
|
160
|
+
return line;
|
|
44
161
|
}
|
|
45
162
|
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
163
|
+
// `basePath` and `pretty` only ever apply to the path of a stack frame,
|
|
164
|
+
// never to the error message or the function name.
|
|
165
|
+
let newPath = path;
|
|
50
166
|
|
|
51
|
-
// Apply user's path filter if provided
|
|
52
|
-
return pathFilter ? pathFilter(match) : true;
|
|
53
|
-
})
|
|
54
|
-
.filter(line => line.trim() !== '')
|
|
55
|
-
.map(line => {
|
|
56
167
|
if (basePathRegex) {
|
|
57
|
-
|
|
168
|
+
newPath = newPath.replaceAll(basePathRegex, '');
|
|
58
169
|
}
|
|
59
170
|
|
|
60
171
|
if (pretty) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
172
|
+
// Convert file:// URLs to regular paths first. A stack can hold anything,
|
|
173
|
+
// so a URL we cannot convert is left as-is instead of throwing. `?` and
|
|
174
|
+
// `#` are left alone too, as they truncate the frame at that character,
|
|
175
|
+
// and so is `%0A`/`%0D`, as decoding it splits the frame into two lines.
|
|
176
|
+
if (newPath.startsWith('file://') && !/[#?]|%0[ad]/iv.test(newPath)) {
|
|
177
|
+
try {
|
|
178
|
+
// The path a file URL stands for is native, and so uses `\` on
|
|
179
|
+
// Windows, while the rest of the stack has already become `/`.
|
|
180
|
+
newPath = fileURLToPath(newPath).replaceAll('\\', '/');
|
|
181
|
+
} catch {}
|
|
182
|
+
}
|
|
73
183
|
|
|
74
|
-
|
|
75
|
-
|
|
184
|
+
// Then replace home directory with ~, but only when the path really
|
|
185
|
+
// lives in it, so `/Users/sindresorhus-2` is left alone.
|
|
186
|
+
if (homeDirectory && (newPath === homeDirectory || newPath.startsWith(homeDirectory + '/'))) {
|
|
187
|
+
newPath = '~' + newPath.slice(homeDirectory.length);
|
|
188
|
+
}
|
|
76
189
|
}
|
|
77
190
|
|
|
78
|
-
return line;
|
|
191
|
+
return line.slice(0, pathIndex) + newPath + line.slice(pathIndex + path.length);
|
|
79
192
|
})
|
|
80
193
|
.join('\n');
|
|
81
194
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cjser/clean-stack",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0-cjser.2",
|
|
4
4
|
"description": "Clean up error stack traces",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -8,11 +8,7 @@
|
|
|
8
8
|
"url": "https://code.moenext.com/3rdeye/cjser.git"
|
|
9
9
|
},
|
|
10
10
|
"funding": "https://github.com/sponsors/sindresorhus",
|
|
11
|
-
"author":
|
|
12
|
-
"name": "Sindre Sorhus",
|
|
13
|
-
"email": "sindresorhus@gmail.com",
|
|
14
|
-
"url": "https://sindresorhus.com"
|
|
15
|
-
},
|
|
11
|
+
"author": "Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)",
|
|
16
12
|
"type": "module",
|
|
17
13
|
"exports": {
|
|
18
14
|
"types": "./index.d.ts",
|
|
@@ -21,10 +17,10 @@
|
|
|
21
17
|
},
|
|
22
18
|
"sideEffects": false,
|
|
23
19
|
"engines": {
|
|
24
|
-
"node": ">=
|
|
20
|
+
"node": ">=22.3"
|
|
25
21
|
},
|
|
26
22
|
"scripts": {
|
|
27
|
-
"test": "xo &&
|
|
23
|
+
"test": "xo && node --test test.js"
|
|
28
24
|
},
|
|
29
25
|
"files": [
|
|
30
26
|
"index.js",
|
|
@@ -44,8 +40,7 @@
|
|
|
44
40
|
"@cjser/url-extras": "0.1.0-cjser.2"
|
|
45
41
|
},
|
|
46
42
|
"devDependencies": {
|
|
47
|
-
"
|
|
48
|
-
"xo": "^0.56.0"
|
|
43
|
+
"xo": "^5.0.1"
|
|
49
44
|
},
|
|
50
45
|
"xo": {
|
|
51
46
|
"rules": {
|
|
@@ -55,18 +50,18 @@
|
|
|
55
50
|
"types": "./index.d.ts",
|
|
56
51
|
"main": "./dist-cjser/index.cjs",
|
|
57
52
|
"cjser": {
|
|
58
|
-
"sourceVersion": "
|
|
53
|
+
"sourceVersion": "7.0.0",
|
|
59
54
|
"cjserVersion": 2,
|
|
60
55
|
"original": {
|
|
61
56
|
"name": "clean-stack",
|
|
62
|
-
"version": "
|
|
57
|
+
"version": "7.0.0",
|
|
63
58
|
"exports": {
|
|
64
59
|
"types": "./index.d.ts",
|
|
65
60
|
"default": "./index.js"
|
|
66
61
|
},
|
|
67
62
|
"repository": "sindresorhus/clean-stack",
|
|
68
63
|
"dependencies": {
|
|
69
|
-
"escape-string-regexp": "5.0.0",
|
|
64
|
+
"escape-string-regexp": "^5.0.0",
|
|
70
65
|
"url-extras": "^0.1.0"
|
|
71
66
|
},
|
|
72
67
|
"files": [
|
|
@@ -74,7 +69,7 @@
|
|
|
74
69
|
"index.d.ts"
|
|
75
70
|
],
|
|
76
71
|
"scripts": {
|
|
77
|
-
"test": "xo &&
|
|
72
|
+
"test": "xo && node --test test.js"
|
|
78
73
|
}
|
|
79
74
|
}
|
|
80
75
|
}
|
package/readme.md
CHANGED
|
@@ -42,13 +42,13 @@ Error: Missing unicorn
|
|
|
42
42
|
|
|
43
43
|
### cleanStack(stack, options?)
|
|
44
44
|
|
|
45
|
-
Returns the cleaned stack or `undefined` if the given `stack` is
|
|
45
|
+
Returns the cleaned stack or `undefined` if the given `stack` is not a string.
|
|
46
46
|
|
|
47
47
|
#### stack
|
|
48
48
|
|
|
49
49
|
Type: `string | undefined`
|
|
50
50
|
|
|
51
|
-
The `stack` property of an [`Error`](https://github.com/microsoft/TypeScript/blob/eac073894b172ec719ca7f28b0b94fc6e6e7d4cf/lib/lib.es5.d.ts#L972-L976)
|
|
51
|
+
The `stack` property of an [`Error`](https://github.com/microsoft/TypeScript/blob/eac073894b172ec719ca7f28b0b94fc6e6e7d4cf/lib/lib.es5.d.ts#L972-L976), or `undefined`.
|
|
52
52
|
|
|
53
53
|
#### options
|
|
54
54
|
|
|
@@ -64,7 +64,9 @@ Prettify the file paths in the stack:
|
|
|
64
64
|
- `/Users/sindresorhus/dev/clean-stack/unicorn.js:2:15` → `~/dev/clean-stack/unicorn.js:2:15`
|
|
65
65
|
- `file:///Users/sindresorhus/dev/clean-stack/unicorn.js:2:15` → `~/dev/clean-stack/unicorn.js:2:15`
|
|
66
66
|
|
|
67
|
-
When enabled, file URLs are converted to regular paths for better readability and IDE integration.
|
|
67
|
+
When enabled, file URLs are converted to regular paths for better readability and IDE integration. URLs that cannot be converted, contain `?` or `#`, or decode to a line break are left unchanged.
|
|
68
|
+
|
|
69
|
+
`pretty` and `basePath` only rewrite frame paths. Backslashes become `/` throughout each frame. Error message text is preserved except for blank lines; lines shaped like frames are treated as frames.
|
|
68
70
|
|
|
69
71
|
##### basePath
|
|
70
72
|
|
|
@@ -72,15 +74,26 @@ Type: `string?`
|
|
|
72
74
|
|
|
73
75
|
Remove the given base path from stack trace file paths, effectively turning absolute paths into relative ones. It will also transform absolute file URLs into relative paths.
|
|
74
76
|
|
|
77
|
+
Supports absolute or relative paths, either separator, and the filesystem root. A leading slash only matches absolute paths. Relative base paths do not match inside `file:///`, except Windows drive paths such as `C:/Users`.
|
|
78
|
+
|
|
75
79
|
Example with `'/Users/sindresorhus/dev/clean-stack'` as `basePath`:
|
|
76
80
|
|
|
77
81
|
`/Users/sindresorhus/dev/clean-stack/unicorn.js:2:15` → `unicorn.js:2:15`
|
|
78
82
|
|
|
83
|
+
> [!NOTE]
|
|
84
|
+
> `basePath` matches before file URLs are decoded, so `/Users/John Doe/dev` does not match `file:///Users/John%20Doe/dev`. After a match, remaining URL escapes stay encoded, even with `pretty`.
|
|
85
|
+
|
|
86
|
+
> [!NOTE]
|
|
87
|
+
> Frames like `at fn (unicorn.js:2:15)` are removed because they resemble old internal Node.js frames. Cleaning again after `basePath` produces this form can therefore drop frames for files directly in the base path. Paths with a directory, like `at fn (src/unicorn.js:2:15)`, and bare frames like `at unicorn.js:2:15` are unaffected.
|
|
88
|
+
|
|
79
89
|
##### pathFilter
|
|
80
90
|
|
|
81
91
|
Type: `(path: string) => boolean`
|
|
82
92
|
|
|
83
|
-
Remove
|
|
93
|
+
Remove stack lines where the callback returns `false`. It receives the path without the closing parenthesis, with `/` separators, before `basePath` or `pretty` is applied. A pathless frame such as `at async Promise.all (index 0)` yields `index 0`.
|
|
94
|
+
|
|
95
|
+
> [!NOTE]
|
|
96
|
+
> Balanced parentheses in function names, such as `at render (partial) (/base/a.js:6:9)`, and paths, such as `C:/Program Files (x86)/app.js:1:1`, are supported. Unbalanced parentheses in a function name, such as `at a(b (/base/a.js:6:9)`, can cause part of the name to be included in the path.
|
|
84
97
|
|
|
85
98
|
```js
|
|
86
99
|
import cleanStack from 'clean-stack';
|