stackFrames

tcmts.cli.main.stackFrames · #7ixczhkb · term · runs on the server

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;
}

used by