posthog-node 5.8.8 → 5.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/dist/{index.d.ts → client.d.ts} +7 -378
  2. package/dist/client.d.ts.map +1 -0
  3. package/dist/client.js +481 -0
  4. package/dist/client.mjs +437 -0
  5. package/dist/entrypoints/index.edge.d.ts +6 -0
  6. package/dist/entrypoints/index.edge.d.ts.map +1 -0
  7. package/dist/entrypoints/index.edge.js +89 -0
  8. package/dist/entrypoints/index.edge.mjs +12 -0
  9. package/dist/entrypoints/index.node.d.ts +6 -0
  10. package/dist/entrypoints/index.node.d.ts.map +1 -0
  11. package/dist/entrypoints/index.node.js +99 -0
  12. package/dist/entrypoints/index.node.mjs +16 -0
  13. package/dist/exports.d.ts +4 -0
  14. package/dist/exports.d.ts.map +1 -0
  15. package/dist/exports.js +78 -0
  16. package/dist/exports.mjs +3 -0
  17. package/dist/extensions/error-tracking/autocapture.d.ts +4 -0
  18. package/dist/extensions/error-tracking/autocapture.d.ts.map +1 -0
  19. package/dist/extensions/error-tracking/autocapture.js +68 -0
  20. package/dist/extensions/error-tracking/autocapture.mjs +31 -0
  21. package/dist/extensions/error-tracking/chunk-ids.d.ts +5 -0
  22. package/dist/extensions/error-tracking/chunk-ids.d.ts.map +1 -0
  23. package/dist/extensions/error-tracking/chunk-ids.js +68 -0
  24. package/dist/extensions/error-tracking/chunk-ids.mjs +34 -0
  25. package/dist/extensions/error-tracking/context-lines.node.d.ts +5 -0
  26. package/dist/extensions/error-tracking/context-lines.node.d.ts.map +1 -0
  27. package/dist/extensions/error-tracking/context-lines.node.js +227 -0
  28. package/dist/extensions/error-tracking/context-lines.node.mjs +187 -0
  29. package/dist/extensions/error-tracking/error-conversion.d.ts +4 -0
  30. package/dist/extensions/error-tracking/error-conversion.d.ts.map +1 -0
  31. package/dist/extensions/error-tracking/error-conversion.js +183 -0
  32. package/dist/extensions/error-tracking/error-conversion.mjs +146 -0
  33. package/dist/extensions/error-tracking/get-module.node.d.ts +3 -0
  34. package/dist/extensions/error-tracking/get-module.node.d.ts.map +1 -0
  35. package/dist/extensions/error-tracking/get-module.node.js +57 -0
  36. package/dist/extensions/error-tracking/get-module.node.mjs +23 -0
  37. package/dist/extensions/error-tracking/index.d.ts +20 -0
  38. package/dist/extensions/error-tracking/index.d.ts.map +1 -0
  39. package/dist/extensions/error-tracking/index.js +97 -0
  40. package/dist/extensions/error-tracking/index.mjs +63 -0
  41. package/dist/extensions/error-tracking/reduceable-cache.d.ts +13 -0
  42. package/dist/extensions/error-tracking/reduceable-cache.d.ts.map +1 -0
  43. package/dist/extensions/error-tracking/reduceable-cache.js +57 -0
  44. package/dist/extensions/error-tracking/reduceable-cache.mjs +23 -0
  45. package/dist/extensions/error-tracking/stack-parser.d.ts +3 -0
  46. package/dist/extensions/error-tracking/stack-parser.d.ts.map +1 -0
  47. package/dist/extensions/error-tracking/stack-parser.js +148 -0
  48. package/dist/extensions/error-tracking/stack-parser.mjs +114 -0
  49. package/dist/extensions/error-tracking/type-checking.d.ts +8 -0
  50. package/dist/extensions/error-tracking/type-checking.d.ts.map +1 -0
  51. package/dist/extensions/error-tracking/type-checking.js +80 -0
  52. package/dist/extensions/error-tracking/type-checking.mjs +31 -0
  53. package/dist/extensions/error-tracking/types.d.ts +61 -0
  54. package/dist/extensions/error-tracking/types.d.ts.map +1 -0
  55. package/dist/extensions/error-tracking/types.js +43 -0
  56. package/dist/extensions/error-tracking/types.mjs +9 -0
  57. package/dist/extensions/express.d.ts +17 -0
  58. package/dist/extensions/express.d.ts.map +1 -0
  59. package/dist/extensions/express.js +61 -0
  60. package/dist/extensions/express.mjs +17 -0
  61. package/dist/extensions/feature-flags/crypto-helpers.d.ts +3 -0
  62. package/dist/extensions/feature-flags/crypto-helpers.d.ts.map +1 -0
  63. package/dist/extensions/feature-flags/crypto-helpers.js +77 -0
  64. package/dist/extensions/feature-flags/crypto-helpers.mjs +22 -0
  65. package/dist/extensions/feature-flags/crypto.d.ts +2 -0
  66. package/dist/extensions/feature-flags/crypto.d.ts.map +1 -0
  67. package/dist/extensions/feature-flags/crypto.js +47 -0
  68. package/dist/extensions/feature-flags/crypto.mjs +13 -0
  69. package/dist/extensions/feature-flags/feature-flags.d.ts +89 -0
  70. package/dist/extensions/feature-flags/feature-flags.d.ts.map +1 -0
  71. package/dist/extensions/feature-flags/feature-flags.js +529 -0
  72. package/dist/extensions/feature-flags/feature-flags.mjs +483 -0
  73. package/dist/extensions/feature-flags/lazy.d.ts +24 -0
  74. package/dist/extensions/feature-flags/lazy.d.ts.map +1 -0
  75. package/dist/extensions/feature-flags/lazy.js +60 -0
  76. package/dist/extensions/feature-flags/lazy.mjs +26 -0
  77. package/dist/extensions/sentry-integration.d.ts +54 -0
  78. package/dist/extensions/sentry-integration.d.ts.map +1 -0
  79. package/dist/extensions/sentry-integration.js +113 -0
  80. package/dist/extensions/sentry-integration.mjs +73 -0
  81. package/dist/storage-memory.d.ts +7 -0
  82. package/dist/storage-memory.d.ts.map +1 -0
  83. package/dist/storage-memory.js +46 -0
  84. package/dist/storage-memory.mjs +12 -0
  85. package/dist/types.d.ts +253 -0
  86. package/dist/types.d.ts.map +1 -0
  87. package/dist/types.js +18 -0
  88. package/dist/types.mjs +0 -0
  89. package/dist/utils/logger.d.ts +3 -0
  90. package/dist/utils/logger.d.ts.map +1 -0
  91. package/dist/utils/logger.js +63 -0
  92. package/dist/utils/logger.mjs +29 -0
  93. package/dist/version.d.ts +2 -0
  94. package/dist/version.d.ts.map +1 -0
  95. package/dist/version.js +36 -0
  96. package/dist/version.mjs +2 -0
  97. package/package.json +32 -31
  98. package/src/client.ts +1532 -0
  99. package/src/entrypoints/index.edge.ts +15 -0
  100. package/src/entrypoints/index.node.ts +17 -0
  101. package/src/exports.ts +3 -0
  102. package/src/extensions/error-tracking/autocapture.ts +65 -0
  103. package/src/extensions/error-tracking/chunk-ids.ts +58 -0
  104. package/src/extensions/error-tracking/context-lines.node.ts +392 -0
  105. package/src/extensions/error-tracking/error-conversion.ts +291 -0
  106. package/src/extensions/error-tracking/get-module.node.ts +57 -0
  107. package/src/extensions/error-tracking/index.ts +103 -0
  108. package/src/extensions/error-tracking/reduceable-cache.ts +39 -0
  109. package/src/extensions/error-tracking/stack-parser.ts +212 -0
  110. package/src/extensions/error-tracking/type-checking.ts +40 -0
  111. package/src/extensions/error-tracking/types.ts +71 -0
  112. package/src/extensions/express.ts +39 -0
  113. package/src/extensions/feature-flags/crypto-helpers.ts +36 -0
  114. package/src/extensions/feature-flags/crypto.ts +22 -0
  115. package/src/extensions/feature-flags/feature-flags.ts +1003 -0
  116. package/src/extensions/feature-flags/lazy.ts +55 -0
  117. package/src/extensions/sentry-integration.ts +216 -0
  118. package/src/storage-memory.ts +13 -0
  119. package/src/types.ts +294 -0
  120. package/src/utils/logger.ts +39 -0
  121. package/src/version.ts +1 -0
  122. package/dist/edge/index.cjs +0 -3150
  123. package/dist/edge/index.cjs.map +0 -1
  124. package/dist/edge/index.mjs +0 -3144
  125. package/dist/edge/index.mjs.map +0 -1
  126. package/dist/node/index.cjs +0 -3556
  127. package/dist/node/index.cjs.map +0 -1
  128. package/dist/node/index.mjs +0 -3550
  129. package/dist/node/index.mjs.map +0 -1
@@ -0,0 +1,15 @@
1
+ export * from '../exports'
2
+
3
+ import ErrorTracking from '../extensions/error-tracking'
4
+
5
+ import { PostHogBackendClient } from '../client'
6
+ import { createStackParser } from '../extensions/error-tracking/stack-parser'
7
+
8
+ ErrorTracking.stackParser = createStackParser()
9
+ ErrorTracking.frameModifiers = []
10
+
11
+ export class PostHog extends PostHogBackendClient {
12
+ getLibraryId(): string {
13
+ return 'posthog-edge'
14
+ }
15
+ }
@@ -0,0 +1,17 @@
1
+ export * from '../exports'
2
+
3
+ import { createGetModuleFromFilename } from '../extensions/error-tracking/get-module.node'
4
+ import { addSourceContext } from '../extensions/error-tracking/context-lines.node'
5
+ import ErrorTracking from '../extensions/error-tracking'
6
+
7
+ import { PostHogBackendClient } from '../client'
8
+ import { createStackParser } from '../extensions/error-tracking/stack-parser'
9
+
10
+ ErrorTracking.stackParser = createStackParser(createGetModuleFromFilename())
11
+ ErrorTracking.frameModifiers = [addSourceContext]
12
+
13
+ export class PostHog extends PostHogBackendClient {
14
+ getLibraryId(): string {
15
+ return 'posthog-node'
16
+ }
17
+ }
package/src/exports.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './extensions/sentry-integration'
2
+ export * from './extensions/express'
3
+ export * from './types'
@@ -0,0 +1,65 @@
1
+ // Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry
2
+ // Licensed under the MIT License
3
+
4
+ import { EventHint } from './types'
5
+
6
+ type ErrorHandler = { _posthogErrorHandler: boolean } & ((error: Error) => void)
7
+
8
+ function makeUncaughtExceptionHandler(
9
+ captureFn: (exception: Error, hint: EventHint) => void,
10
+ onFatalFn: (exception: Error) => void
11
+ ): ErrorHandler {
12
+ let calledFatalError: boolean = false
13
+
14
+ return Object.assign(
15
+ (error: Error): void => {
16
+ // Attaching a listener to `uncaughtException` will prevent the node process from exiting. We generally do not
17
+ // want to alter this behaviour so we check for other listeners that users may have attached themselves and adjust
18
+ // exit behaviour of the SDK accordingly:
19
+ // - If other listeners are attached, do not exit.
20
+ // - If the only listener attached is ours, exit.
21
+ const userProvidedListenersCount = global.process.listeners('uncaughtException').filter((listener) => {
22
+ // There are 2 listeners we ignore:
23
+ return (
24
+ // as soon as we're using domains this listener is attached by node itself
25
+ listener.name !== 'domainUncaughtExceptionClear' &&
26
+ // the handler we register in this integration
27
+ (listener as ErrorHandler)._posthogErrorHandler !== true
28
+ )
29
+ }).length
30
+
31
+ const processWouldExit = userProvidedListenersCount === 0
32
+
33
+ captureFn(error, {
34
+ mechanism: {
35
+ type: 'onuncaughtexception',
36
+ handled: false,
37
+ },
38
+ })
39
+
40
+ if (!calledFatalError && processWouldExit) {
41
+ calledFatalError = true
42
+ onFatalFn(error)
43
+ }
44
+ },
45
+ { _posthogErrorHandler: true }
46
+ )
47
+ }
48
+
49
+ export function addUncaughtExceptionListener(
50
+ captureFn: (exception: Error, hint: EventHint) => void,
51
+ onFatalFn: (exception: Error) => void
52
+ ): void {
53
+ global.process.on('uncaughtException', makeUncaughtExceptionHandler(captureFn, onFatalFn))
54
+ }
55
+
56
+ export function addUnhandledRejectionListener(captureFn: (exception: unknown, hint: EventHint) => void): void {
57
+ global.process.on('unhandledRejection', (reason: unknown) => {
58
+ return captureFn(reason, {
59
+ mechanism: {
60
+ type: 'onunhandledrejection',
61
+ handled: false,
62
+ },
63
+ })
64
+ })
65
+ }
@@ -0,0 +1,58 @@
1
+ // Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry
2
+ // Licensed under the MIT License
3
+
4
+ import type { StackParser } from './types'
5
+
6
+ type StackString = string
7
+ type CachedResult = [string, string]
8
+
9
+ type ChunkIdMapType = Record<string, string>
10
+
11
+ let parsedStackResults: Record<StackString, CachedResult> | undefined
12
+ let lastKeysCount: number | undefined
13
+ let cachedFilenameChunkIds: ChunkIdMapType | undefined
14
+
15
+ export function getFilenameToChunkIdMap(stackParser: StackParser): ChunkIdMapType | null {
16
+ const chunkIdMap = (globalThis as any)._posthogChunkIds as ChunkIdMapType | undefined
17
+ if (!chunkIdMap) {
18
+ return null
19
+ }
20
+
21
+ const chunkIdKeys = Object.keys(chunkIdMap)
22
+
23
+ if (cachedFilenameChunkIds && chunkIdKeys.length === lastKeysCount) {
24
+ return cachedFilenameChunkIds
25
+ }
26
+
27
+ lastKeysCount = chunkIdKeys.length
28
+
29
+ cachedFilenameChunkIds = chunkIdKeys.reduce<Record<string, string>>((acc, stackKey) => {
30
+ if (!parsedStackResults) {
31
+ parsedStackResults = {}
32
+ }
33
+
34
+ const result = parsedStackResults[stackKey]
35
+
36
+ if (result) {
37
+ acc[result[0]] = result[1]
38
+ } else {
39
+ const parsedStack = stackParser(stackKey)
40
+
41
+ for (let i = parsedStack.length - 1; i >= 0; i--) {
42
+ const stackFrame = parsedStack[i]
43
+ const filename = stackFrame?.filename
44
+ const chunkId = chunkIdMap[stackKey]
45
+
46
+ if (filename && chunkId) {
47
+ acc[filename] = chunkId
48
+ parsedStackResults[stackKey] = [filename, chunkId]
49
+ break
50
+ }
51
+ }
52
+ }
53
+
54
+ return acc
55
+ }, {})
56
+
57
+ return cachedFilenameChunkIds
58
+ }
@@ -0,0 +1,392 @@
1
+ // Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry
2
+ // Licensed under the MIT License
3
+
4
+ import { StackFrame } from './types'
5
+ import { ReduceableCache } from './reduceable-cache'
6
+ import { createReadStream } from 'node:fs'
7
+ import { createInterface } from 'node:readline'
8
+
9
+ const LRU_FILE_CONTENTS_CACHE = new ReduceableCache<string, Record<number, string>>(25)
10
+ const LRU_FILE_CONTENTS_FS_READ_FAILED = new ReduceableCache<string, 1>(20)
11
+ const DEFAULT_LINES_OF_CONTEXT = 7
12
+ // Determines the upper bound of lineno/colno that we will attempt to read. Large colno values are likely to be
13
+ // minified code while large lineno values are likely to be bundled code.
14
+ // Exported for testing purposes.
15
+ export const MAX_CONTEXTLINES_COLNO: number = 1000
16
+ export const MAX_CONTEXTLINES_LINENO: number = 10000
17
+
18
+ type ReadlineRange = [start: number, end: number]
19
+
20
+ export async function addSourceContext(frames: StackFrame[]): Promise<StackFrame[]> {
21
+ // keep a lookup map of which files we've already enqueued to read,
22
+ // so we don't enqueue the same file multiple times which would cause multiple i/o reads
23
+ const filesToLines: Record<string, number[]> = {}
24
+
25
+ // Maps preserve insertion order, so we iterate in reverse, starting at the
26
+ // outermost frame and closer to where the exception has occurred (poor mans priority)
27
+ for (let i = frames.length - 1; i >= 0; i--) {
28
+ const frame: StackFrame | undefined = frames[i]
29
+ const filename = frame?.filename
30
+
31
+ if (
32
+ !frame ||
33
+ typeof filename !== 'string' ||
34
+ typeof frame.lineno !== 'number' ||
35
+ shouldSkipContextLinesForFile(filename) ||
36
+ shouldSkipContextLinesForFrame(frame)
37
+ ) {
38
+ continue
39
+ }
40
+
41
+ const filesToLinesOutput = filesToLines[filename]
42
+ if (!filesToLinesOutput) {
43
+ filesToLines[filename] = []
44
+ }
45
+ filesToLines[filename].push(frame.lineno)
46
+ }
47
+
48
+ const files = Object.keys(filesToLines)
49
+ if (files.length == 0) {
50
+ return frames
51
+ }
52
+
53
+ const readlinePromises: Promise<void>[] = []
54
+ for (const file of files) {
55
+ // If we failed to read this before, dont try reading it again.
56
+ if (LRU_FILE_CONTENTS_FS_READ_FAILED.get(file)) {
57
+ continue
58
+ }
59
+
60
+ const filesToLineRanges = filesToLines[file]
61
+ if (!filesToLineRanges) {
62
+ continue
63
+ }
64
+
65
+ // Sort ranges so that they are sorted by line increasing order and match how the file is read.
66
+ filesToLineRanges.sort((a, b) => a - b)
67
+ // Check if the contents are already in the cache and if we can avoid reading the file again.
68
+ const ranges = makeLineReaderRanges(filesToLineRanges)
69
+ if (ranges.every((r) => rangeExistsInContentCache(file, r))) {
70
+ continue
71
+ }
72
+
73
+ const cache = emplace(LRU_FILE_CONTENTS_CACHE, file, {})
74
+ readlinePromises.push(getContextLinesFromFile(file, ranges, cache))
75
+ }
76
+
77
+ // The promise rejections are caught in order to prevent them from short circuiting Promise.all
78
+ await Promise.all(readlinePromises).catch(() => {})
79
+
80
+ // Perform the same loop as above, but this time we can assume all files are in the cache
81
+ // and attempt to add source context to frames.
82
+ if (frames && frames.length > 0) {
83
+ addSourceContextToFrames(frames, LRU_FILE_CONTENTS_CACHE)
84
+ }
85
+
86
+ // Once we're finished processing an exception reduce the files held in the cache
87
+ // so that we don't indefinetly increase the size of this map
88
+ LRU_FILE_CONTENTS_CACHE.reduce()
89
+
90
+ return frames
91
+ }
92
+
93
+ /**
94
+ * Extracts lines from a file and stores them in a cache.
95
+ */
96
+ function getContextLinesFromFile(path: string, ranges: ReadlineRange[], output: Record<number, string>): Promise<void> {
97
+ return new Promise((resolve) => {
98
+ // It is important *not* to have any async code between createInterface and the 'line' event listener
99
+ // as it will cause the 'line' event to
100
+ // be emitted before the listener is attached.
101
+ const stream = createReadStream(path)
102
+ const lineReaded = createInterface({
103
+ input: stream,
104
+ })
105
+
106
+ // We need to explicitly destroy the stream to prevent memory leaks,
107
+ // removing the listeners on the readline interface is not enough.
108
+ // See: https://github.com/nodejs/node/issues/9002 and https://github.com/getsentry/sentry-javascript/issues/14892
109
+ function destroyStreamAndResolve(): void {
110
+ stream.destroy()
111
+ resolve()
112
+ }
113
+
114
+ // Init at zero and increment at the start of the loop because lines are 1 indexed.
115
+ let lineNumber = 0
116
+ let currentRangeIndex = 0
117
+ const range = ranges[currentRangeIndex]
118
+ if (range === undefined) {
119
+ // We should never reach this point, but if we do, we should resolve the promise to prevent it from hanging.
120
+ destroyStreamAndResolve()
121
+ return
122
+ }
123
+ let rangeStart = range[0]
124
+ let rangeEnd = range[1]
125
+
126
+ // We use this inside Promise.all, so we need to resolve the promise even if there is an error
127
+ // to prevent Promise.all from short circuiting the rest.
128
+ function onStreamError(): void {
129
+ // Mark file path as failed to read and prevent multiple read attempts.
130
+ LRU_FILE_CONTENTS_FS_READ_FAILED.set(path, 1)
131
+ lineReaded.close()
132
+ lineReaded.removeAllListeners()
133
+ destroyStreamAndResolve()
134
+ }
135
+
136
+ // We need to handle the error event to prevent the process from crashing in < Node 16
137
+ // https://github.com/nodejs/node/pull/31603
138
+ stream.on('error', onStreamError)
139
+ lineReaded.on('error', onStreamError)
140
+ lineReaded.on('close', destroyStreamAndResolve)
141
+
142
+ lineReaded.on('line', (line) => {
143
+ lineNumber++
144
+ if (lineNumber < rangeStart) {
145
+ return
146
+ }
147
+
148
+ // !Warning: This mutates the cache by storing the snipped line into the cache.
149
+ output[lineNumber] = snipLine(line, 0)
150
+
151
+ if (lineNumber >= rangeEnd) {
152
+ if (currentRangeIndex === ranges.length - 1) {
153
+ // We need to close the file stream and remove listeners, else the reader will continue to run our listener;
154
+ lineReaded.close()
155
+ lineReaded.removeAllListeners()
156
+ return
157
+ }
158
+ currentRangeIndex++
159
+ const range = ranges[currentRangeIndex]
160
+ if (range === undefined) {
161
+ // This should never happen as it means we have a bug in the context.
162
+ lineReaded.close()
163
+ lineReaded.removeAllListeners()
164
+ return
165
+ }
166
+ rangeStart = range[0]
167
+ rangeEnd = range[1]
168
+ }
169
+ })
170
+ })
171
+ }
172
+
173
+ /** Adds context lines to frames */
174
+ function addSourceContextToFrames(frames: StackFrame[], cache: ReduceableCache<string, Record<number, string>>): void {
175
+ for (const frame of frames) {
176
+ // Only add context if we have a filename and it hasn't already been added
177
+ if (frame.filename && frame.context_line === undefined && typeof frame.lineno === 'number') {
178
+ const contents = cache.get(frame.filename)
179
+ if (contents === undefined) {
180
+ continue
181
+ }
182
+
183
+ addContextToFrame(frame.lineno, frame, contents)
184
+ }
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Resolves context lines before and after the given line number and appends them to the frame;
190
+ */
191
+ function addContextToFrame(lineno: number, frame: StackFrame, contents: Record<number, string> | undefined): void {
192
+ // When there is no line number in the frame, attaching context is nonsensical and will even break grouping.
193
+ // We already check for lineno before calling this, but since StackFrame lineno is optional, we check it again.
194
+ if (frame.lineno === undefined || contents === undefined) {
195
+ return
196
+ }
197
+
198
+ frame.pre_context = []
199
+ for (let i = makeRangeStart(lineno); i < lineno; i++) {
200
+ // We always expect the start context as line numbers cannot be negative. If we dont find a line, then
201
+ // something went wrong somewhere. Clear the context and return without adding any linecontext.
202
+ const line = contents[i]
203
+ if (line === undefined) {
204
+ clearLineContext(frame)
205
+ return
206
+ }
207
+
208
+ frame.pre_context.push(line)
209
+ }
210
+
211
+ // We should always have the context line. If we dont, something went wrong, so we clear the context and return
212
+ // without adding any linecontext.
213
+ if (contents[lineno] === undefined) {
214
+ clearLineContext(frame)
215
+ return
216
+ }
217
+
218
+ frame.context_line = contents[lineno]
219
+
220
+ const end = makeRangeEnd(lineno)
221
+ frame.post_context = []
222
+ for (let i = lineno + 1; i <= end; i++) {
223
+ // Since we dont track when the file ends, we cant clear the context if we dont find a line as it could
224
+ // just be that we reached the end of the file.
225
+ const line = contents[i]
226
+ if (line === undefined) {
227
+ break
228
+ }
229
+ frame.post_context.push(line)
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Clears the context lines from a frame, used to reset a frame to its original state
235
+ * if we fail to resolve all context lines for it.
236
+ */
237
+ function clearLineContext(frame: StackFrame): void {
238
+ delete frame.pre_context
239
+ delete frame.context_line
240
+ delete frame.post_context
241
+ }
242
+
243
+ /**
244
+ * Determines if context lines should be skipped for a file.
245
+ * - .min.(mjs|cjs|js) files are and not useful since they dont point to the original source
246
+ * - node: prefixed modules are part of the runtime and cannot be resolved to a file
247
+ * - data: skip json, wasm and inline js https://nodejs.org/api/esm.html#data-imports
248
+ */
249
+ function shouldSkipContextLinesForFile(path: string): boolean {
250
+ // Test the most common prefix and extension first. These are the ones we
251
+ // are most likely to see in user applications and are the ones we can break out of first.
252
+ return (
253
+ path.startsWith('node:') ||
254
+ path.endsWith('.min.js') ||
255
+ path.endsWith('.min.cjs') ||
256
+ path.endsWith('.min.mjs') ||
257
+ path.startsWith('data:')
258
+ )
259
+ }
260
+
261
+ /**
262
+ * Determines if we should skip contextlines based off the max lineno and colno values.
263
+ */
264
+ function shouldSkipContextLinesForFrame(frame: StackFrame): boolean {
265
+ if (frame.lineno !== undefined && frame.lineno > MAX_CONTEXTLINES_LINENO) {
266
+ return true
267
+ }
268
+ if (frame.colno !== undefined && frame.colno > MAX_CONTEXTLINES_COLNO) {
269
+ return true
270
+ }
271
+ return false
272
+ }
273
+
274
+ /**
275
+ * Checks if we have all the contents that we need in the cache.
276
+ */
277
+ function rangeExistsInContentCache(file: string, range: ReadlineRange): boolean {
278
+ const contents = LRU_FILE_CONTENTS_CACHE.get(file)
279
+ if (contents === undefined) {
280
+ return false
281
+ }
282
+
283
+ for (let i = range[0]; i <= range[1]; i++) {
284
+ if (contents[i] === undefined) {
285
+ return false
286
+ }
287
+ }
288
+
289
+ return true
290
+ }
291
+
292
+ /**
293
+ * Creates contiguous ranges of lines to read from a file. In the case where context lines overlap,
294
+ * the ranges are merged to create a single range.
295
+ */
296
+ function makeLineReaderRanges(lines: number[]): ReadlineRange[] {
297
+ if (!lines.length) {
298
+ return []
299
+ }
300
+
301
+ let i = 0
302
+ const line = lines[0]
303
+
304
+ if (typeof line !== 'number') {
305
+ return []
306
+ }
307
+
308
+ let current = makeContextRange(line)
309
+ const out: ReadlineRange[] = []
310
+ while (true) {
311
+ if (i === lines.length - 1) {
312
+ out.push(current)
313
+ break
314
+ }
315
+
316
+ // If the next line falls into the current range, extend the current range to lineno + linecontext.
317
+ const next = lines[i + 1]
318
+ if (typeof next !== 'number') {
319
+ break
320
+ }
321
+ if (next <= current[1]) {
322
+ current[1] = next + DEFAULT_LINES_OF_CONTEXT
323
+ } else {
324
+ out.push(current)
325
+ current = makeContextRange(next)
326
+ }
327
+
328
+ i++
329
+ }
330
+
331
+ return out
332
+ }
333
+ // Determine start and end indices for context range (inclusive);
334
+ function makeContextRange(line: number): [start: number, end: number] {
335
+ return [makeRangeStart(line), makeRangeEnd(line)]
336
+ }
337
+ // Compute inclusive end context range
338
+ function makeRangeStart(line: number): number {
339
+ return Math.max(1, line - DEFAULT_LINES_OF_CONTEXT)
340
+ }
341
+ // Compute inclusive start context range
342
+ function makeRangeEnd(line: number): number {
343
+ return line + DEFAULT_LINES_OF_CONTEXT
344
+ }
345
+
346
+ /**
347
+ * Get or init map value
348
+ */
349
+ function emplace<T extends ReduceableCache<K, V>, K extends string, V>(map: T, key: K, contents: V): V {
350
+ const value = map.get(key)
351
+
352
+ if (value === undefined) {
353
+ map.set(key, contents)
354
+ return contents
355
+ }
356
+
357
+ return value
358
+ }
359
+
360
+ function snipLine(line: string, colno: number): string {
361
+ let newLine = line
362
+ const lineLength = newLine.length
363
+ if (lineLength <= 150) {
364
+ return newLine
365
+ }
366
+ if (colno > lineLength) {
367
+ colno = lineLength
368
+ }
369
+
370
+ let start = Math.max(colno - 60, 0)
371
+ if (start < 5) {
372
+ start = 0
373
+ }
374
+
375
+ let end = Math.min(start + 140, lineLength)
376
+ if (end > lineLength - 5) {
377
+ end = lineLength
378
+ }
379
+ if (end === lineLength) {
380
+ start = Math.max(end - 140, 0)
381
+ }
382
+
383
+ newLine = newLine.slice(start, end)
384
+ if (start > 0) {
385
+ newLine = `...${newLine}`
386
+ }
387
+ if (end < lineLength) {
388
+ newLine += '...'
389
+ }
390
+
391
+ return newLine
392
+ }