bf630e8e57
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
215 lines
6.1 KiB
TypeScript
215 lines
6.1 KiB
TypeScript
import * as React from 'react'
|
|
import { StackFrame } from 'next/dist/compiled/stacktrace-parser'
|
|
import { CodeFrame } from '../components/CodeFrame'
|
|
import { ReadyRuntimeError } from '../helpers/getErrorByType'
|
|
import { noop as css } from '../helpers/noop-template'
|
|
import { getFrameSource, OriginalStackFrame } from '../helpers/stack-frame'
|
|
|
|
export type RuntimeErrorProps = { error: ReadyRuntimeError }
|
|
|
|
const CallStackFrame: React.FC<{
|
|
frame: OriginalStackFrame
|
|
}> = function CallStackFrame({ frame }) {
|
|
// TODO: ability to expand resolved frames
|
|
// TODO: render error or external indicator
|
|
|
|
const f: StackFrame = frame.originalStackFrame ?? frame.sourceStackFrame
|
|
const hasSource = Boolean(frame.originalCodeFrame)
|
|
|
|
const open = React.useCallback(() => {
|
|
if (!hasSource) return
|
|
|
|
const params = new URLSearchParams()
|
|
for (const key in f) {
|
|
params.append(key, ((f as any)[key] ?? '').toString())
|
|
}
|
|
|
|
self
|
|
.fetch(
|
|
`${
|
|
process.env.__NEXT_ROUTER_BASEPATH || ''
|
|
}/__nextjs_launch-editor?${params.toString()}`
|
|
)
|
|
.then(
|
|
() => {},
|
|
() => {
|
|
console.error('There was an issue opening this code in your editor.')
|
|
}
|
|
)
|
|
}, [hasSource, f])
|
|
|
|
return (
|
|
<div data-nextjs-call-stack-frame>
|
|
<h6 data-nextjs-frame-expanded={Boolean(frame.expanded)}>
|
|
{f.methodName}
|
|
</h6>
|
|
<div
|
|
data-has-source={hasSource ? 'true' : undefined}
|
|
tabIndex={hasSource ? 10 : undefined}
|
|
role={hasSource ? 'link' : undefined}
|
|
onClick={open}
|
|
title={hasSource ? 'Click to open in your editor' : undefined}
|
|
>
|
|
<span>{getFrameSource(f)}</span>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
strokeWidth="2"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
>
|
|
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
|
|
<polyline points="15 3 21 3 21 9"></polyline>
|
|
<line x1="10" y1="14" x2="21" y2="3"></line>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const RuntimeError: React.FC<RuntimeErrorProps> = function RuntimeError({
|
|
error,
|
|
}) {
|
|
const firstFirstPartyFrameIndex = React.useMemo<number>(() => {
|
|
return error.frames.findIndex(
|
|
(entry) =>
|
|
entry.expanded &&
|
|
Boolean(entry.originalCodeFrame) &&
|
|
Boolean(entry.originalStackFrame)
|
|
)
|
|
}, [error.frames])
|
|
const firstFrame = React.useMemo<OriginalStackFrame | null>(() => {
|
|
return error.frames[firstFirstPartyFrameIndex] ?? null
|
|
}, [error.frames, firstFirstPartyFrameIndex])
|
|
|
|
const allLeadingFrames = React.useMemo<OriginalStackFrame[]>(
|
|
() =>
|
|
firstFirstPartyFrameIndex < 0
|
|
? []
|
|
: error.frames.slice(0, firstFirstPartyFrameIndex),
|
|
[error.frames, firstFirstPartyFrameIndex]
|
|
)
|
|
|
|
const [all, setAll] = React.useState(firstFrame == null)
|
|
const toggleAll = React.useCallback(() => {
|
|
setAll((v) => !v)
|
|
}, [])
|
|
|
|
const leadingFrames = React.useMemo(
|
|
() => allLeadingFrames.filter((f) => f.expanded || all),
|
|
[all, allLeadingFrames]
|
|
)
|
|
const allCallStackFrames = React.useMemo<OriginalStackFrame[]>(
|
|
() => error.frames.slice(firstFirstPartyFrameIndex + 1),
|
|
[error.frames, firstFirstPartyFrameIndex]
|
|
)
|
|
const visibleCallStackFrames = React.useMemo<OriginalStackFrame[]>(
|
|
() => allCallStackFrames.filter((f) => f.expanded || all),
|
|
[all, allCallStackFrames]
|
|
)
|
|
|
|
const canShowMore = React.useMemo<boolean>(() => {
|
|
return (
|
|
allCallStackFrames.length !== visibleCallStackFrames.length ||
|
|
(all && firstFrame != null)
|
|
)
|
|
}, [
|
|
all,
|
|
allCallStackFrames.length,
|
|
firstFrame,
|
|
visibleCallStackFrames.length,
|
|
])
|
|
|
|
return (
|
|
<React.Fragment>
|
|
{firstFrame ? (
|
|
<React.Fragment>
|
|
<h5>Source</h5>
|
|
{leadingFrames.map((frame, index) => (
|
|
<CallStackFrame
|
|
key={`leading-frame-${index}-${all}`}
|
|
frame={frame}
|
|
/>
|
|
))}
|
|
<CodeFrame
|
|
stackFrame={firstFrame.originalStackFrame!}
|
|
codeFrame={firstFrame.originalCodeFrame!}
|
|
/>
|
|
</React.Fragment>
|
|
) : undefined}
|
|
{visibleCallStackFrames.length ? (
|
|
<React.Fragment>
|
|
<h5>Call Stack</h5>
|
|
{visibleCallStackFrames.map((frame, index) => (
|
|
<CallStackFrame key={`call-stack-${index}-${all}`} frame={frame} />
|
|
))}
|
|
</React.Fragment>
|
|
) : undefined}
|
|
{canShowMore ? (
|
|
<React.Fragment>
|
|
<button
|
|
tabIndex={10}
|
|
data-nextjs-data-runtime-error-collapsed-action
|
|
type="button"
|
|
onClick={toggleAll}
|
|
>
|
|
{all ? 'Hide' : 'Show'} collapsed frames
|
|
</button>
|
|
</React.Fragment>
|
|
) : undefined}
|
|
</React.Fragment>
|
|
)
|
|
}
|
|
|
|
export const styles = css`
|
|
button[data-nextjs-data-runtime-error-collapsed-action] {
|
|
background: none;
|
|
border: none;
|
|
padding: 0;
|
|
font-size: var(--size-font-small);
|
|
line-height: var(--size-font-bigger);
|
|
color: var(--color-accents-3);
|
|
}
|
|
|
|
[data-nextjs-call-stack-frame]:not(:last-child) {
|
|
margin-bottom: var(--size-gap-double);
|
|
}
|
|
|
|
[data-nextjs-call-stack-frame] > h6 {
|
|
margin-top: 0;
|
|
margin-bottom: var(--size-gap);
|
|
font-family: var(--font-stack-monospace);
|
|
color: #222;
|
|
}
|
|
[data-nextjs-call-stack-frame] > h6[data-nextjs-frame-expanded='false'] {
|
|
color: #666;
|
|
}
|
|
[data-nextjs-call-stack-frame] > div {
|
|
display: flex;
|
|
align-items: center;
|
|
padding-left: calc(var(--size-gap) + var(--size-gap-half));
|
|
font-size: var(--size-font-small);
|
|
color: #999;
|
|
}
|
|
[data-nextjs-call-stack-frame] > div > svg {
|
|
width: auto;
|
|
height: var(--size-font-small);
|
|
margin-left: var(--size-gap);
|
|
|
|
display: none;
|
|
}
|
|
|
|
[data-nextjs-call-stack-frame] > div[data-has-source] {
|
|
cursor: pointer;
|
|
}
|
|
[data-nextjs-call-stack-frame] > div[data-has-source]:hover {
|
|
text-decoration: underline dotted;
|
|
}
|
|
[data-nextjs-call-stack-frame] > div[data-has-source] > svg {
|
|
display: unset;
|
|
}
|
|
`
|
|
|
|
export { RuntimeError }
|