stackFrames
uses node:url
Where each of a stack's frames is: a file and line, as V8 writes them (at f (<where>:1:2), at <where>:1:2) and
Firefox and Safari do (f@<where>:1:2), paths with spaces too. A file: URL is its path; a page's module in
tcm dev (http://…/@fs/<path>?v=…) is its path; a production build's chunk (http://…/_app/assets/<chunk>,
under a base too) is that asset; another address isn't a file, and is left out. Lines and columns count from 1.
import { fileURLToPath } from "node:url"; export function stackFrames( stack: string, ): { file?: string; asset?: string; line: number; column: number }[] { const out: { file?: string; asset?: string; line: number; column: number }[] = []; for (const frame of stack.split("\n").slice(0, 40)) { const m = /(?:\(|^\s*at |@)((?:file:\/\/|https?:\/\/[^/\s()]+)?\/[^()]+?):(\d+):(\d+)\)?\s*$/.exec( frame, ); if (!m) continue; const line = Number(m[2]); const column = Number(m[3]); let file = m[1]!; try { if (file.startsWith("file://")) file = fileURLToPath(file); else if (/^https?:/.test(file)) { file = decodeURIComponent(new URL(file).pathname); const asset = /\/_app\/assets\/([^/]+\.m?js)$/.exec(file)?.[1]; if (asset) { out.push({ asset, line, column }); continue; } if (!file.startsWith("/@fs/")) continue; file = file.slice("/@fs".length); } } catch { continue; } out.push({ file: file.split("?")[0]!, line, column }); } return out; }