🌳
pt0/serverF/throwErrorF/sourceMapStackTraceF.mts
1import { ptDir } from '../ptDirF.mts'
2import { difference } from 'lodash-es'
3import fs from 'fs'
4import { SourceMapConsumer, SourceMapGenerator } from 'source-map'
6import { parse } from 'stacktrace-parser'
10// https://github.com/mifi/stacktracify/blob/master/index.js
12const getLocalSmPathForLine = (lineS: string) => {
13 // Match both _next/ (client URL) and .next/ (server filesystem path)
14 const match = lineS.match(/[_.]next\/(.+\.js)/)
15 if (!match) return
16 return ptDir + `/${getAppName()}/.next/` + match[1] + '.map'
19const getSm = async (smPath: string) => {
20 return await new SourceMapConsumer(fs.readFileSync(smPath, 'utf-8'))
23let memo: SourceMapConsumer | undefined
24const getCombinedSmc = async ({mapPaths}: {mapPaths: string[]}) => {
25 if (memo) return memo
27 const [firstPath, ...restPaths] = mapPaths
29 let generator = SourceMapGenerator.fromSourceMap(await getSm(firstPath))
30 await chainPromiseCalls(restPaths, async (path) => {
31 const smm = await getSm(path)
32 generator.applySourceMap(smm)
33 })
34 memo = await new SourceMapConsumer(generator.toString())
35 return memo
38export const mapStackTrace = async ({stackA}: {stackA: string[]}) => {
40 let [header, ...lines] = stackA
42 let mapPaths: string[] = [...new Set(lines.map(getLocalSmPathForLine).filter(Boolean) as string[])]
43 const mapPathsExist = mapPaths.filter((path: string) => fs.existsSync(path))
44 const sourceMapsNotExist = difference(mapPaths, mapPathsExist)
45 if (sourceMapsNotExist.length > 0) {
46 console.log({sourceMapsNotExist})
47 }
48 mapPaths = mapPathsExist
50 if (mapPaths.length == 0) return stackA
52 const smc = await getCombinedSmc({mapPaths})
54 lines = lines.map((line) => {
55 // stacktrace-parser doesn't seem to support stacktrace lines like this:
56 // index-12345678.js:1:2 a
57 const match = line.match(/^(\s+)([^\s]+:\d+:\d+)\s+([^\s]+)$/)
58 if (match) {
59 return `${match[1]}at ${match[3]} (${match[2]})`
60 }
62 return line
63 })
65 const stack = parse(lines.join('\n'))
68 return stack.map((stackItem, ii) => {
69 const { methodName, lineNumber, column, file } = stackItem
71 try {
72 if (lineNumber == null || lineNumber < 1) {
73 return ` at ${methodName || '[unknown]'}`
74 } else {
75 const pos = smc.originalPositionFor({ line: lineNumber, column: column! });
76 if (pos && pos.line != null) {
77 return ` at ${pos.name || methodName || '[unknown]'} (${pos.source}:${pos.line}:${pos.column})`
78 }
79 }
80 return (
81 'utt' + // cleanup that^ crap repl w small searchable token
82 lines[ii]
83 )
84 } catch (err) {
85 console.log('FAILED_TO_PARSE_LINE', err)
86 return ` at FAILED_TO_PARSE_LINE`
87 }
88 }).filter(Boolean)