vercel/next.js · #99888
Don't parse error message lines as stack frames in the server error inspector
packages/next/src/server/patch-error-inspect.ts49 + / 2 −
@@ -164,16 +164,63 @@ function computeErrorName(error: Error): string { return error.name || 'Error' } +// Records where the frames start in a stack produced by+// `prepareUnsourcemappedStackTrace`, so they can be parsed without having to+// guess where the message ends. Uses `Symbol.for` since this module is bundled+// into several runtimes, and any copy may have formatted a given stack.+const STACK_FRAMES_START = Symbol.for('next.dev.stackFramesStart')+type ErrorWithStackFramesStart = Error & {+ [STACK_FRAMES_START]?: { stack: string; framesStart: number }+}+ function prepareUnsourcemappedStackTrace( error: Error, structuredStackTrace: any[] ): string { const name = computeErrorName(error) const message = error.message || '' let stack = name + ': ' + message+ const framesStart = stack.length for (let i = 0; i < structuredStackTrace.length; i++) { stack += '\n at ' + structuredStackTrace[i].toString() }+ // Record where the real stack frames start+ try {+ // Non-enumerable so that it doesn't show up when inspecting the error.+ Object.defineProperty(error, STACK_FRAMES_START, {+ value: { stack, framesStart },+ configurable: true,+ writable: true,+ })+ } catch {+ // The error may be frozen. We'll fall back to finding the frames by+ // matching the message.+ }+ return stack+}++/**+ * Returns the part of `error.stack` after the message, so the stack parser+ * can't mistake message lines for frames (e.g. a line starting with a URL or+ * with "webpack"/"turbopack").+ */+function getUnparsedStackFrames(error: Error, errorName: string): string {+ let stack = String(error.stack)+ const recorded = (error as ErrorWithStackFramesStart)[STACK_FRAMES_START]+ // `error.stack` may have been replaced after we formatted it.+ if (recorded !== undefined && recorded.stack === stack) {+ return stack.slice(recorded.framesStart)+ }+ // Errors printing by `prepareUnsourcemappedStackTrace` are `Error: <message>` so attempt to strip that prefix+ if (stack.startsWith(errorName)) {+ stack = stack.slice(errorName.length)+ }+ if (stack.startsWith(': ')) {+ stack = stack.slice(': '.length)+ }+ if (stack.startsWith(error.message)) {+ stack = stack.slice(error.message.length)+ } return stack } @@ -435,12 +482,12 @@ function parseAndSourceMap( inspectOptions: util.InspectOptions ): string { const showIgnoreListed = process.env.__NEXT_SHOW_IGNORE_LISTED === 'true'- // We overwrote Error.prepareStackTrace earlier so error.stack is not sourcemapped.- let unparsedStack = String(error.stack) // We could just read it from `error.stack`. // This works around cases where a 3rd party `Error.prepareStackTrace` implementation // doesn't implement the name computation correctly. const errorName = computeErrorName(error)+ // We overwrote Error.prepareStackTrace earlier so error.stack is not sourcemapped.+ let unparsedStack = getUnparsedStackFrames(error, errorName) let idx = unparsedStack.indexOf('react_stack_bottom_frame') if (idx !== -1) {test/e2e/app-dir/proxy-runtime/proxy-runtime.test.ts1 + / 1 −
@@ -37,7 +37,7 @@ describe('proxy-runtime', () => { 4 | The exported configuration object in a source file needs to have a very specific format from which some properties can be statically parsed at compiled-time. https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config- at <unknown> (https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config)"+ at ignore-listed frames" `) } else { expect(cliOutput).toContain(test/e2e/turbopack-css-node-process-connect-failure/index.test.ts2 + / 0 −
@@ -59,5 +59,7 @@ describe('turbopack css node process connect failure', () => { // This is not a Turbopack bug, so don't present it as one. expect(cliOutput).not.toContain('TurbopackInternalError') expect(cliOutput).not.toContain('https://bugs.nextjs.org/')+ // Lines of the message must not be mistaken for stack frames.+ expect(cliOutput).not.toContain('at <unknown>') }) })