mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
[bun dev] Implement copy as markdown
This commit is contained in:
@@ -5,12 +5,12 @@
|
||||
"Source Code Pro", "Fira Mono", "Droid Sans Mono", "Courier New", monospace;
|
||||
}
|
||||
|
||||
a {
|
||||
:host a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
:host a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
#BunErrorOverlay-container {
|
||||
@@ -32,7 +32,7 @@ a:hover {
|
||||
Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
}
|
||||
|
||||
#BunErrorOverlay-container a {
|
||||
:host a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ a:hover {
|
||||
|
||||
.BunError-Summary {
|
||||
display: grid;
|
||||
grid-template-columns: min-content auto min-content;
|
||||
grid-template-columns: min-content auto min-content min-content;
|
||||
grid-template-rows: 46px;
|
||||
align-items: center;
|
||||
padding: 0 18px;
|
||||
@@ -69,11 +69,16 @@ a:hover {
|
||||
}
|
||||
|
||||
.BunError-footer {
|
||||
display: grid;
|
||||
padding: 12px 18px;
|
||||
justify-content: flex-end;
|
||||
display: flex;
|
||||
border-top: 1px solid rgb(220, 220, 220);
|
||||
align-items: center;
|
||||
|
||||
grid-template-columns: auto auto;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.BunError-footerItem {
|
||||
padding: 12px 18px;
|
||||
}
|
||||
|
||||
.BunError-Summary-Title {
|
||||
@@ -142,24 +147,113 @@ a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.BunError-SourceLines-lines a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.BunError-SourceLine-text::selection,
|
||||
.BunError-SourceLine-text *::selection {
|
||||
background-color: #6437e3;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.BunError-SourceLine-text:active:before {
|
||||
width: 61px;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.BunError-SourceLines-lines {
|
||||
}
|
||||
|
||||
.BunError-SourceLines {
|
||||
.BunError-SourceLine {
|
||||
display: grid;
|
||||
grid-template-columns: min-content auto;
|
||||
grid-template-rows: repeat(16px, 6);
|
||||
column-gap: 13px;
|
||||
}
|
||||
|
||||
.BunError-SourceLines {
|
||||
font-size: 14px;
|
||||
padding-left: 24px;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.BunError-CopyButton {
|
||||
font-weight: 500;
|
||||
font-size: 1em;
|
||||
vertical-align: middle;
|
||||
display: flex;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
border-right: 1px solid #ccc;
|
||||
background: #fcfcfc;
|
||||
border-bottom-left-radius: 12px;
|
||||
color: #333;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
transition: transform 0.1s linear;
|
||||
}
|
||||
|
||||
.BunError-CopyButton,
|
||||
#BunError-poweredBy {
|
||||
padding: 12px 12px;
|
||||
}
|
||||
.BunError-Summary-help {
|
||||
display: flex;
|
||||
white-space: nowrap;
|
||||
gap: 9px;
|
||||
margin-right: 18px;
|
||||
|
||||
border-right: 1px solid rgb(220, 220, 220);
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
padding-right: 18px;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.BunError-Summary-help svg {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
#BunErrorOverlay-container .BunError-Summary-help {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
#BunErrorOverlay-container .BunError-Summary-help:hover {
|
||||
color: #5865f2;
|
||||
}
|
||||
|
||||
#BunErrorOverlay-container .BunError-Summary-help:hover svg {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.BunError-CopyButton svg {
|
||||
width: 18px;
|
||||
margin-right: 0.7em;
|
||||
opacity: 0.75;
|
||||
margin-left: 6px;
|
||||
fill: currentColor;
|
||||
stroke: currentColor;
|
||||
}
|
||||
|
||||
.BunError-CopyButton:hover {
|
||||
background: #6437e3;
|
||||
color: white;
|
||||
|
||||
border-right-color: #6437e3;
|
||||
}
|
||||
|
||||
.BunError-CopyButton:active {
|
||||
transform: scale(1.03, 1.03);
|
||||
transform-origin: center center;
|
||||
}
|
||||
|
||||
.BunError-SourceLine-text {
|
||||
white-space: pre;
|
||||
cursor: text;
|
||||
display: block;
|
||||
position: relative;
|
||||
|
||||
font-family: var(--bun-error-monospace);
|
||||
}
|
||||
@@ -167,21 +261,41 @@ a:hover {
|
||||
.BunError-SourceLine-number {
|
||||
font-variant: tabular-nums;
|
||||
display: block;
|
||||
border-left: 4px solid transparent;
|
||||
margin-left: -1px;
|
||||
cursor: pointer;
|
||||
padding-left: 14px;
|
||||
padding-right: 12px;
|
||||
text-align: right;
|
||||
user-select: none;
|
||||
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.BunError-SourceLine-number,
|
||||
.BunError-SourceLine-number * {
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.BunError-SourceLine-number:active + .BunError-SourceLine-text {
|
||||
background-color: #6437e3;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.BunError-SourceLine-number:hover {
|
||||
background-color: #7443fa;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.BunError-SourceLine:hover .BunError-SourceLine-number {
|
||||
border-left-color: #7443fa;
|
||||
}
|
||||
|
||||
.BunError-SourceLine-number--empty {
|
||||
color: rgb(165, 165, 165);
|
||||
}
|
||||
|
||||
.BunError-SourceLine-number:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.BunError-SourceLine-number,
|
||||
.BunError-SourceLine-text {
|
||||
height: 18px;
|
||||
@@ -196,7 +310,7 @@ a:hover {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.BunError-SourceLine-text--highlight {
|
||||
#BunErrorOverlay-container .BunError-SourceLine-text--highlight {
|
||||
color: #e33737;
|
||||
}
|
||||
|
||||
@@ -288,12 +402,13 @@ a:hover {
|
||||
.BunError-StackFrames {
|
||||
display: table;
|
||||
table-layout: auto;
|
||||
padding: 16px 12px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
margin: 0 auto;
|
||||
border-radius: 4px;
|
||||
line-height: 1.2;
|
||||
border-collapse: separate;
|
||||
border-spacing: 12px 6px;
|
||||
|
||||
background-color: rgb(244, 244, 244);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
187
packages/bun-error/stack-trace-parser.ts
Normal file
187
packages/bun-error/stack-trace-parser.ts
Normal file
@@ -0,0 +1,187 @@
|
||||
const UNKNOWN_FUNCTION = "<unknown>";
|
||||
import type {
|
||||
FallbackMessageContainer,
|
||||
JSException,
|
||||
Location,
|
||||
Message,
|
||||
SourceLine,
|
||||
StackFrame,
|
||||
WebsocketMessageBuildFailure,
|
||||
} from "../../src/api/schema";
|
||||
|
||||
/**
|
||||
* This parses the different stack traces and puts them into one format
|
||||
* This borrows heavily from TraceKit (https://github.com/csnover/TraceKit)
|
||||
*/
|
||||
export function parse(stackString): StackFrame[] {
|
||||
const lines = stackString.split("\n");
|
||||
|
||||
return lines.reduce((stack, line) => {
|
||||
const parseResult =
|
||||
parseChrome(line) ||
|
||||
parseWinjs(line) ||
|
||||
parseGecko(line) ||
|
||||
parseNode(line) ||
|
||||
parseJSC(line);
|
||||
|
||||
if (parseResult) {
|
||||
stack.push(parseResult);
|
||||
}
|
||||
|
||||
return stack;
|
||||
}, []);
|
||||
}
|
||||
|
||||
const formatFile = (file) => {
|
||||
if (!file) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (file.startsWith("blob:")) {
|
||||
if (globalThis["__BUN"]?.client) {
|
||||
const replacement =
|
||||
globalThis["__BUN"]?.client.dependencies.getFilePathFromBlob(file);
|
||||
if (replacement) {
|
||||
file = replacement;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var _file = String(file);
|
||||
if (_file.startsWith(globalThis.location?.origin)) {
|
||||
_file = _file.substring(globalThis.location?.origin.length);
|
||||
}
|
||||
|
||||
while (_file.startsWith("/")) {
|
||||
_file = _file.substring(1);
|
||||
}
|
||||
|
||||
if (_file.endsWith(".bun")) {
|
||||
_file = "node_modules.bun";
|
||||
}
|
||||
|
||||
return _file;
|
||||
};
|
||||
|
||||
const chromeRe =
|
||||
/^\s*at (.*?) ?\(((?:file|https?|blob|chrome-extension|native|eval|webpack|<anonymous>|\/|[a-z]:\\|\\\\).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i;
|
||||
const chromeEvalRe = /\((\S*)(?::(\d+))(?::(\d+))\)/;
|
||||
|
||||
function parseChrome(line) {
|
||||
const parts = chromeRe.exec(line);
|
||||
|
||||
if (!parts) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isNative = parts[2] && parts[2].indexOf("native") === 0; // start of line
|
||||
const isEval = parts[2] && parts[2].indexOf("eval") === 0; // start of line
|
||||
|
||||
const submatch = chromeEvalRe.exec(parts[2]);
|
||||
if (isEval && submatch != null) {
|
||||
// throw out eval line/column and use top-most line/column number
|
||||
parts[2] = submatch[1]; // url
|
||||
parts[3] = submatch[2]; // line
|
||||
parts[4] = submatch[3]; // column
|
||||
}
|
||||
|
||||
return {
|
||||
file: formatFile(!isNative ? parts[2] : null),
|
||||
function_name: parts[1] || "",
|
||||
position: {
|
||||
line: parts[3] ? +parts[3] : null,
|
||||
column_start: parts[4] ? +parts[4] : null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const winjsRe =
|
||||
/^\s*at (?:((?:\[object object\])?.+) )?\(?((?:file|ms-appx|https?|webpack|blob):.*?):(\d+)(?::(\d+))?\)?\s*$/i;
|
||||
|
||||
function parseWinjs(line) {
|
||||
const parts = winjsRe.exec(line);
|
||||
|
||||
if (!parts) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
file: formatFile(parts[2]),
|
||||
function_name: parts[1],
|
||||
position: {
|
||||
line: +parts[3],
|
||||
column_start: parts[4] ? +parts[4] : null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const geckoRe =
|
||||
/^\s*(.*?)(?:\((.*?)\))?(?:^|@)((?:file|https?|blob|chrome|webpack|resource|\[native).*?|[^@]*bundle)(?::(\d+))?(?::(\d+))?\s*$/i;
|
||||
const geckoEvalRe = /(\S+) line (\d+)(?: > eval line \d+)* > eval/i;
|
||||
|
||||
function parseGecko(line) {
|
||||
const parts = geckoRe.exec(line);
|
||||
|
||||
if (!parts) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isEval = parts[3] && parts[3].indexOf(" > eval") > -1;
|
||||
|
||||
const submatch = geckoEvalRe.exec(parts[3]);
|
||||
if (isEval && submatch != null) {
|
||||
// throw out eval line/column and use top-most line number
|
||||
parts[3] = submatch[1];
|
||||
parts[4] = submatch[2];
|
||||
parts[5] = null; // no column when eval
|
||||
}
|
||||
|
||||
return {
|
||||
file: formatFile(parts[3]),
|
||||
function_name: parts[1] || "",
|
||||
position: {
|
||||
line: parts[4] ? +parts[4] : null,
|
||||
column_start: parts[5] ? +parts[5] : null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const javaScriptCoreRe =
|
||||
/^\s*(?:([^@]*)(?:\((.*?)\))?@)?(\S.*?):(\d+)(?::(\d+))?\s*$/i;
|
||||
|
||||
function parseJSC(line) {
|
||||
const parts = javaScriptCoreRe.exec(line);
|
||||
|
||||
if (!parts) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
file: formatFile(parts[3]),
|
||||
function_name: parts[1] || "",
|
||||
position: {
|
||||
line: +parts[4],
|
||||
column_start: parts[5] ? +parts[5] : null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const nodeRe =
|
||||
/^\s*at (?:((?:\[object object\])?[^\\/]+(?: \[as \S+\])?) )?\(?(.*?):(\d+)(?::(\d+))?\)?\s*$/i;
|
||||
|
||||
function parseNode(line) {
|
||||
const parts = nodeRe.exec(line);
|
||||
|
||||
if (!parts) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
file: formatFile(parts[2]),
|
||||
function_name: parts[1] || "",
|
||||
position: {
|
||||
line: +parts[3],
|
||||
column_start: parts[4] ? +parts[4] : null,
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user