posthog-node
Advanced tools
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.node.d.ts","sourceRoot":"","sources":["../../src/entrypoints/index.node.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAM1B,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,EAAE,aAAa,IAAI,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA;AAE9D,qBAAa,OAAQ,SAAQ,oBAAoB;IAC/C,YAAY,IAAI,MAAM;IAItB,SAAS,CAAC,iBAAiB,IAAI,cAAc;cAI1B,4BAA4B,IAAI,iBAAiB,CAAC,sBAAsB;CAa5F"} | ||
| {"version":3,"file":"index.node.d.ts","sourceRoot":"","sources":["../../src/entrypoints/index.node.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAM1B,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,EAAE,aAAa,IAAI,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA;AAE9D,qBAAa,OAAQ,SAAQ,oBAAoB;IAC/C,YAAY,IAAI,MAAM;IAItB,SAAS,CAAC,iBAAiB,IAAI,cAAc;cAI1B,4BAA4B,IAAI,iBAAiB,CAAC,sBAAsB;CAiB5F"} |
@@ -102,3 +102,3 @@ "use strict"; | ||
| (0, _extensions_error_tracking_modifiers_module_node__WEBPACK_IMPORTED_MODULE_1__.createModulerModifier)(), | ||
| _extensions_error_tracking_modifiers_context_lines_node__WEBPACK_IMPORTED_MODULE_2__.addSourceContext, | ||
| (frames)=>(0, _extensions_error_tracking_modifiers_context_lines_node__WEBPACK_IMPORTED_MODULE_2__.addSourceContext)(frames, void 0, this._logger), | ||
| (0, _extensions_error_tracking_modifiers_relative_path_node__WEBPACK_IMPORTED_MODULE_3__.createRelativePathModifier)() | ||
@@ -105,0 +105,0 @@ ]); |
@@ -24,3 +24,3 @@ import { createModulerModifier } from "../extensions/error-tracking/modifiers/module.node.mjs"; | ||
| createModulerModifier(), | ||
| addSourceContext, | ||
| (frames)=>addSourceContext(frames, void 0, this._logger), | ||
| createRelativePathModifier() | ||
@@ -27,0 +27,0 @@ ]); |
@@ -1,5 +0,9 @@ | ||
| import { ErrorTracking as CoreErrorTracking } from '@posthog/core'; | ||
| import { ErrorTracking as CoreErrorTracking, type Logger } from '@posthog/core'; | ||
| import { type FileHandle } from 'node:fs/promises'; | ||
| export declare const MAX_CONTEXTLINES_COLNO: number; | ||
| export declare const MAX_CONTEXTLINES_LINENO: number; | ||
| export declare function addSourceContext(frames: CoreErrorTracking.StackFrame[]): Promise<CoreErrorTracking.StackFrame[]>; | ||
| export declare const MAX_CONTEXTLINES_FILE_SIZE: number; | ||
| type OpenSourceFile = (path: string, flags: number) => Promise<FileHandle>; | ||
| export declare function addSourceContext(frames: CoreErrorTracking.StackFrame[], openSourceFile?: OpenSourceFile, logger?: Pick<Logger, 'debug'>): Promise<CoreErrorTracking.StackFrame[]>; | ||
| export {}; | ||
| //# sourceMappingURL=context-lines.node.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"context-lines.node.d.ts","sourceRoot":"","sources":["../../../../src/extensions/error-tracking/modifiers/context-lines.node.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,IAAI,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAUlE,eAAO,MAAM,sBAAsB,EAAE,MAAa,CAAA;AAClD,eAAO,MAAM,uBAAuB,EAAE,MAAc,CAAA;AAIpD,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,iBAAiB,CAAC,UAAU,EAAE,GACrC,OAAO,CAAC,iBAAiB,CAAC,UAAU,EAAE,CAAC,CAuEzC"} | ||
| {"version":3,"file":"context-lines.node.d.ts","sourceRoot":"","sources":["../../../../src/extensions/error-tracking/modifiers/context-lines.node.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,IAAI,iBAAiB,EAAE,KAAK,MAAM,EAAE,MAAM,eAAe,CAAA;AAE/E,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAUxD,eAAO,MAAM,sBAAsB,EAAE,MAAa,CAAA;AAClD,eAAO,MAAM,uBAAuB,EAAE,MAAc,CAAA;AACpD,eAAO,MAAM,0BAA0B,EAAE,MAAyB,CAAA;AAGlE,KAAK,cAAc,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAA;AAE1E,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,iBAAiB,CAAC,UAAU,EAAE,EACtC,cAAc,GAAE,cAAqB,EACrC,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,OAAO,CAAC,iBAAiB,CAAC,UAAU,EAAE,CAAC,CAqFzC"} |
@@ -27,8 +27,11 @@ "use strict"; | ||
| __webpack_require__.d(__webpack_exports__, { | ||
| MAX_CONTEXTLINES_LINENO: ()=>MAX_CONTEXTLINES_LINENO, | ||
| MAX_CONTEXTLINES_FILE_SIZE: ()=>MAX_CONTEXTLINES_FILE_SIZE, | ||
| MAX_CONTEXTLINES_COLNO: ()=>MAX_CONTEXTLINES_COLNO, | ||
| addSourceContext: ()=>addSourceContext | ||
| addSourceContext: ()=>addSourceContext, | ||
| MAX_CONTEXTLINES_LINENO: ()=>MAX_CONTEXTLINES_LINENO | ||
| }); | ||
| const core_namespaceObject = require("@posthog/core"); | ||
| const external_node_fs_namespaceObject = require("node:fs"); | ||
| const promises_namespaceObject = require("node:fs/promises"); | ||
| const external_node_path_namespaceObject = require("node:path"); | ||
| const external_node_readline_namespaceObject = require("node:readline"); | ||
@@ -40,4 +43,9 @@ const LRU_FILE_CONTENTS_CACHE = new core_namespaceObject.ErrorTracking.ReduceableCache(25); | ||
| const MAX_CONTEXTLINES_LINENO = 10000; | ||
| async function addSourceContext(frames) { | ||
| const MAX_CONTEXTLINES_FILE_SIZE = 10485760; | ||
| async function addSourceContext(frames, openSourceFile = promises_namespaceObject.open, logger) { | ||
| const filesToLines = {}; | ||
| let basePath; | ||
| try { | ||
| basePath = process.cwd(); | ||
| } catch {} | ||
| for(let i = frames.length - 1; i >= 0; i--){ | ||
@@ -47,2 +55,3 @@ const frame = frames[i]; | ||
| if (!frame || 'string' != typeof filename || 'number' != typeof frame.lineno || shouldSkipContextLinesForFile(filename) || shouldSkipContextLinesForFrame(frame)) continue; | ||
| if (!(0, external_node_path_namespaceObject.isAbsolute)(filename) && void 0 === basePath) continue; | ||
| const filesToLinesOutput = filesToLines[filename]; | ||
@@ -56,3 +65,5 @@ if (!filesToLinesOutput) filesToLines[filename] = []; | ||
| for (const file of files){ | ||
| if (LRU_FILE_CONTENTS_FS_READ_FAILED.get(file)) continue; | ||
| const cacheKey = makeSourceCacheKey(file, basePath); | ||
| if (void 0 === cacheKey) continue; | ||
| if (LRU_FILE_CONTENTS_FS_READ_FAILED.get(cacheKey)) continue; | ||
| const filesToLineRanges = filesToLines[file]; | ||
@@ -62,36 +73,76 @@ if (!filesToLineRanges) continue; | ||
| const ranges = makeLineReaderRanges(filesToLineRanges); | ||
| if (ranges.every((r)=>rangeExistsInContentCache(file, r))) continue; | ||
| const cache = emplace(LRU_FILE_CONTENTS_CACHE, file, {}); | ||
| readlinePromises.push(getContextLinesFromFile(file, ranges, cache)); | ||
| if (ranges.every((r)=>rangeExistsInContentCache(cacheKey, r))) continue; | ||
| const cache = emplace(LRU_FILE_CONTENTS_CACHE, cacheKey, {}); | ||
| readlinePromises.push(getContextLinesFromFile(file, ranges, cache, cacheKey, openSourceFile, logger)); | ||
| } | ||
| await Promise.all(readlinePromises).catch(()=>{}); | ||
| if (frames && frames.length > 0) addSourceContextToFrames(frames, LRU_FILE_CONTENTS_CACHE); | ||
| if (frames && frames.length > 0) addSourceContextToFrames(frames, LRU_FILE_CONTENTS_CACHE, basePath); | ||
| LRU_FILE_CONTENTS_CACHE.reduce(); | ||
| return frames; | ||
| } | ||
| function getContextLinesFromFile(path, ranges, output) { | ||
| async function openRegularSourceFile(path, openSourceFile, logger) { | ||
| let fileHandle; | ||
| let isValid = false; | ||
| try { | ||
| fileHandle = await openSourceFile(path, external_node_fs_namespaceObject.constants.O_RDONLY | external_node_fs_namespaceObject.constants.O_NONBLOCK); | ||
| const fileStat = await fileHandle.stat(); | ||
| if (!fileStat.isFile()) return; | ||
| if (fileStat.size > MAX_CONTEXTLINES_FILE_SIZE) return void logger?.debug(`Skipping source context for oversized file ${path}: ${fileStat.size} bytes exceeds ${MAX_CONTEXTLINES_FILE_SIZE}`); | ||
| isValid = true; | ||
| return fileHandle; | ||
| } catch { | ||
| return; | ||
| } finally{ | ||
| if (fileHandle && !isValid) await fileHandle.close().catch(()=>{}); | ||
| } | ||
| } | ||
| async function getContextLinesFromFile(path, ranges, output, cacheKey, openSourceFile, logger) { | ||
| const fileHandle = await openRegularSourceFile(path, openSourceFile, logger); | ||
| if (void 0 === fileHandle) return void LRU_FILE_CONTENTS_FS_READ_FAILED.set(cacheKey, 1); | ||
| const openedFileHandle = fileHandle; | ||
| return new Promise((resolve)=>{ | ||
| const stream = (0, external_node_fs_namespaceObject.createReadStream)(path); | ||
| const lineReaded = (0, external_node_readline_namespaceObject.createInterface)({ | ||
| input: stream | ||
| }); | ||
| function destroyStreamAndResolve() { | ||
| stream.destroy(); | ||
| resolve(); | ||
| let finished = false; | ||
| function destroyStreamAndResolve(stream) { | ||
| if (finished) return; | ||
| finished = true; | ||
| stream?.destroy(); | ||
| openedFileHandle.close().then(resolve, resolve); | ||
| } | ||
| let stream; | ||
| try { | ||
| stream = openedFileHandle.createReadStream({ | ||
| autoClose: false, | ||
| start: 0, | ||
| end: MAX_CONTEXTLINES_FILE_SIZE - 1 | ||
| }); | ||
| } catch { | ||
| LRU_FILE_CONTENTS_FS_READ_FAILED.set(cacheKey, 1); | ||
| destroyStreamAndResolve(); | ||
| return; | ||
| } | ||
| let lineReaded; | ||
| try { | ||
| lineReaded = (0, external_node_readline_namespaceObject.createInterface)({ | ||
| input: stream | ||
| }); | ||
| } catch { | ||
| LRU_FILE_CONTENTS_FS_READ_FAILED.set(cacheKey, 1); | ||
| destroyStreamAndResolve(stream); | ||
| return; | ||
| } | ||
| let lineNumber = 0; | ||
| let currentRangeIndex = 0; | ||
| const range = ranges[currentRangeIndex]; | ||
| if (void 0 === range) return void destroyStreamAndResolve(); | ||
| if (void 0 === range) return void destroyStreamAndResolve(stream); | ||
| let rangeStart = range[0]; | ||
| let rangeEnd = range[1]; | ||
| function onStreamError() { | ||
| LRU_FILE_CONTENTS_FS_READ_FAILED.set(path, 1); | ||
| LRU_FILE_CONTENTS_FS_READ_FAILED.set(cacheKey, 1); | ||
| lineReaded.close(); | ||
| lineReaded.removeAllListeners(); | ||
| destroyStreamAndResolve(); | ||
| destroyStreamAndResolve(stream); | ||
| } | ||
| stream.on('error', onStreamError); | ||
| lineReaded.on('error', onStreamError); | ||
| lineReaded.on('close', destroyStreamAndResolve); | ||
| lineReaded.on('close', ()=>destroyStreamAndResolve(stream)); | ||
| lineReaded.on('line', (line)=>{ | ||
@@ -120,5 +171,6 @@ lineNumber++; | ||
| } | ||
| function addSourceContextToFrames(frames, cache) { | ||
| function addSourceContextToFrames(frames, cache, basePath) { | ||
| for (const frame of frames)if (frame.filename && void 0 === frame.context_line && 'number' == typeof frame.lineno) { | ||
| const contents = cache.get(frame.filename); | ||
| const cacheKey = makeSourceCacheKey(frame.filename, basePath); | ||
| const contents = void 0 === cacheKey ? void 0 : cache.get(cacheKey); | ||
| if (void 0 === contents) continue; | ||
@@ -159,4 +211,14 @@ addContextToFrame(frame.lineno, frame, contents); | ||
| } | ||
| function rangeExistsInContentCache(file, range) { | ||
| const contents = LRU_FILE_CONTENTS_CACHE.get(file); | ||
| function makeSourceCacheKey(path, basePath) { | ||
| if ((0, external_node_path_namespaceObject.isAbsolute)(path)) return JSON.stringify([ | ||
| null, | ||
| path | ||
| ]); | ||
| return void 0 === basePath ? void 0 : JSON.stringify([ | ||
| basePath, | ||
| path | ||
| ]); | ||
| } | ||
| function rangeExistsInContentCache(cacheKey, range) { | ||
| const contents = LRU_FILE_CONTENTS_CACHE.get(cacheKey); | ||
| if (void 0 === contents) return false; | ||
@@ -225,2 +287,3 @@ for(let i = range[0]; i <= range[1]; i++)if (void 0 === contents[i]) return false; | ||
| exports.MAX_CONTEXTLINES_COLNO = __webpack_exports__.MAX_CONTEXTLINES_COLNO; | ||
| exports.MAX_CONTEXTLINES_FILE_SIZE = __webpack_exports__.MAX_CONTEXTLINES_FILE_SIZE; | ||
| exports.MAX_CONTEXTLINES_LINENO = __webpack_exports__.MAX_CONTEXTLINES_LINENO; | ||
@@ -230,2 +293,3 @@ exports.addSourceContext = __webpack_exports__.addSourceContext; | ||
| "MAX_CONTEXTLINES_COLNO", | ||
| "MAX_CONTEXTLINES_FILE_SIZE", | ||
| "MAX_CONTEXTLINES_LINENO", | ||
@@ -232,0 +296,0 @@ "addSourceContext" |
| import { ErrorTracking } from "@posthog/core"; | ||
| import { createReadStream } from "node:fs"; | ||
| import { constants } from "node:fs"; | ||
| import { open as promises_open } from "node:fs/promises"; | ||
| import { isAbsolute } from "node:path"; | ||
| import { createInterface } from "node:readline"; | ||
@@ -9,4 +11,9 @@ const LRU_FILE_CONTENTS_CACHE = new ErrorTracking.ReduceableCache(25); | ||
| const MAX_CONTEXTLINES_LINENO = 10000; | ||
| async function addSourceContext(frames) { | ||
| const MAX_CONTEXTLINES_FILE_SIZE = 10485760; | ||
| async function addSourceContext(frames, openSourceFile = promises_open, logger) { | ||
| const filesToLines = {}; | ||
| let basePath; | ||
| try { | ||
| basePath = process.cwd(); | ||
| } catch {} | ||
| for(let i = frames.length - 1; i >= 0; i--){ | ||
@@ -16,2 +23,3 @@ const frame = frames[i]; | ||
| if (!frame || 'string' != typeof filename || 'number' != typeof frame.lineno || shouldSkipContextLinesForFile(filename) || shouldSkipContextLinesForFrame(frame)) continue; | ||
| if (!isAbsolute(filename) && void 0 === basePath) continue; | ||
| const filesToLinesOutput = filesToLines[filename]; | ||
@@ -25,3 +33,5 @@ if (!filesToLinesOutput) filesToLines[filename] = []; | ||
| for (const file of files){ | ||
| if (LRU_FILE_CONTENTS_FS_READ_FAILED.get(file)) continue; | ||
| const cacheKey = makeSourceCacheKey(file, basePath); | ||
| if (void 0 === cacheKey) continue; | ||
| if (LRU_FILE_CONTENTS_FS_READ_FAILED.get(cacheKey)) continue; | ||
| const filesToLineRanges = filesToLines[file]; | ||
@@ -31,36 +41,76 @@ if (!filesToLineRanges) continue; | ||
| const ranges = makeLineReaderRanges(filesToLineRanges); | ||
| if (ranges.every((r)=>rangeExistsInContentCache(file, r))) continue; | ||
| const cache = emplace(LRU_FILE_CONTENTS_CACHE, file, {}); | ||
| readlinePromises.push(getContextLinesFromFile(file, ranges, cache)); | ||
| if (ranges.every((r)=>rangeExistsInContentCache(cacheKey, r))) continue; | ||
| const cache = emplace(LRU_FILE_CONTENTS_CACHE, cacheKey, {}); | ||
| readlinePromises.push(getContextLinesFromFile(file, ranges, cache, cacheKey, openSourceFile, logger)); | ||
| } | ||
| await Promise.all(readlinePromises).catch(()=>{}); | ||
| if (frames && frames.length > 0) addSourceContextToFrames(frames, LRU_FILE_CONTENTS_CACHE); | ||
| if (frames && frames.length > 0) addSourceContextToFrames(frames, LRU_FILE_CONTENTS_CACHE, basePath); | ||
| LRU_FILE_CONTENTS_CACHE.reduce(); | ||
| return frames; | ||
| } | ||
| function getContextLinesFromFile(path, ranges, output) { | ||
| async function openRegularSourceFile(path, openSourceFile, logger) { | ||
| let fileHandle; | ||
| let isValid = false; | ||
| try { | ||
| fileHandle = await openSourceFile(path, constants.O_RDONLY | constants.O_NONBLOCK); | ||
| const fileStat = await fileHandle.stat(); | ||
| if (!fileStat.isFile()) return; | ||
| if (fileStat.size > MAX_CONTEXTLINES_FILE_SIZE) return void logger?.debug(`Skipping source context for oversized file ${path}: ${fileStat.size} bytes exceeds ${MAX_CONTEXTLINES_FILE_SIZE}`); | ||
| isValid = true; | ||
| return fileHandle; | ||
| } catch { | ||
| return; | ||
| } finally{ | ||
| if (fileHandle && !isValid) await fileHandle.close().catch(()=>{}); | ||
| } | ||
| } | ||
| async function getContextLinesFromFile(path, ranges, output, cacheKey, openSourceFile, logger) { | ||
| const fileHandle = await openRegularSourceFile(path, openSourceFile, logger); | ||
| if (void 0 === fileHandle) return void LRU_FILE_CONTENTS_FS_READ_FAILED.set(cacheKey, 1); | ||
| const openedFileHandle = fileHandle; | ||
| return new Promise((resolve)=>{ | ||
| const stream = createReadStream(path); | ||
| const lineReaded = createInterface({ | ||
| input: stream | ||
| }); | ||
| function destroyStreamAndResolve() { | ||
| stream.destroy(); | ||
| resolve(); | ||
| let finished = false; | ||
| function destroyStreamAndResolve(stream) { | ||
| if (finished) return; | ||
| finished = true; | ||
| stream?.destroy(); | ||
| openedFileHandle.close().then(resolve, resolve); | ||
| } | ||
| let stream; | ||
| try { | ||
| stream = openedFileHandle.createReadStream({ | ||
| autoClose: false, | ||
| start: 0, | ||
| end: MAX_CONTEXTLINES_FILE_SIZE - 1 | ||
| }); | ||
| } catch { | ||
| LRU_FILE_CONTENTS_FS_READ_FAILED.set(cacheKey, 1); | ||
| destroyStreamAndResolve(); | ||
| return; | ||
| } | ||
| let lineReaded; | ||
| try { | ||
| lineReaded = createInterface({ | ||
| input: stream | ||
| }); | ||
| } catch { | ||
| LRU_FILE_CONTENTS_FS_READ_FAILED.set(cacheKey, 1); | ||
| destroyStreamAndResolve(stream); | ||
| return; | ||
| } | ||
| let lineNumber = 0; | ||
| let currentRangeIndex = 0; | ||
| const range = ranges[currentRangeIndex]; | ||
| if (void 0 === range) return void destroyStreamAndResolve(); | ||
| if (void 0 === range) return void destroyStreamAndResolve(stream); | ||
| let rangeStart = range[0]; | ||
| let rangeEnd = range[1]; | ||
| function onStreamError() { | ||
| LRU_FILE_CONTENTS_FS_READ_FAILED.set(path, 1); | ||
| LRU_FILE_CONTENTS_FS_READ_FAILED.set(cacheKey, 1); | ||
| lineReaded.close(); | ||
| lineReaded.removeAllListeners(); | ||
| destroyStreamAndResolve(); | ||
| destroyStreamAndResolve(stream); | ||
| } | ||
| stream.on('error', onStreamError); | ||
| lineReaded.on('error', onStreamError); | ||
| lineReaded.on('close', destroyStreamAndResolve); | ||
| lineReaded.on('close', ()=>destroyStreamAndResolve(stream)); | ||
| lineReaded.on('line', (line)=>{ | ||
@@ -89,5 +139,6 @@ lineNumber++; | ||
| } | ||
| function addSourceContextToFrames(frames, cache) { | ||
| function addSourceContextToFrames(frames, cache, basePath) { | ||
| for (const frame of frames)if (frame.filename && void 0 === frame.context_line && 'number' == typeof frame.lineno) { | ||
| const contents = cache.get(frame.filename); | ||
| const cacheKey = makeSourceCacheKey(frame.filename, basePath); | ||
| const contents = void 0 === cacheKey ? void 0 : cache.get(cacheKey); | ||
| if (void 0 === contents) continue; | ||
@@ -128,4 +179,14 @@ addContextToFrame(frame.lineno, frame, contents); | ||
| } | ||
| function rangeExistsInContentCache(file, range) { | ||
| const contents = LRU_FILE_CONTENTS_CACHE.get(file); | ||
| function makeSourceCacheKey(path, basePath) { | ||
| if (isAbsolute(path)) return JSON.stringify([ | ||
| null, | ||
| path | ||
| ]); | ||
| return void 0 === basePath ? void 0 : JSON.stringify([ | ||
| basePath, | ||
| path | ||
| ]); | ||
| } | ||
| function rangeExistsInContentCache(cacheKey, range) { | ||
| const contents = LRU_FILE_CONTENTS_CACHE.get(cacheKey); | ||
| if (void 0 === contents) return false; | ||
@@ -193,2 +254,2 @@ for(let i = range[0]; i <= range[1]; i++)if (void 0 === contents[i]) return false; | ||
| } | ||
| export { MAX_CONTEXTLINES_COLNO, MAX_CONTEXTLINES_LINENO, addSourceContext }; | ||
| export { MAX_CONTEXTLINES_COLNO, MAX_CONTEXTLINES_FILE_SIZE, MAX_CONTEXTLINES_LINENO, addSourceContext }; |
@@ -1,2 +0,2 @@ | ||
| export declare const version = "5.47.6"; | ||
| export declare const version = "5.47.7"; | ||
| //# sourceMappingURL=version.d.ts.map |
+1
-1
@@ -29,3 +29,3 @@ "use strict"; | ||
| }); | ||
| const version = '5.47.6'; | ||
| const version = '5.47.7'; | ||
| exports.version = __webpack_exports__.version; | ||
@@ -32,0 +32,0 @@ for(var __webpack_i__ in __webpack_exports__)if (-1 === [ |
+1
-1
@@ -1,2 +0,2 @@ | ||
| const version = '5.47.6'; | ||
| const version = '5.47.7'; | ||
| export { version }; |
+1
-1
| { | ||
| "name": "posthog-node", | ||
| "version": "5.47.6", | ||
| "version": "5.47.7", | ||
| "bugs": { | ||
@@ -5,0 +5,0 @@ "url": "https://github.com/PostHog/posthog-js/issues" |
@@ -30,5 +30,9 @@ export * from '../exports' | ||
| CoreErrorTracking.createStackParser('node:javascript', CoreErrorTracking.nodeStackLineParser), | ||
| [createModulerModifier(), addSourceContext, createRelativePathModifier()] | ||
| [ | ||
| createModulerModifier(), | ||
| (frames) => addSourceContext(frames, undefined, this._logger), | ||
| createRelativePathModifier(), | ||
| ] | ||
| ) | ||
| } | ||
| } |
@@ -5,4 +5,6 @@ // Portions of this file are derived from getsentry/sentry-javascript | ||
| import { ErrorTracking as CoreErrorTracking } from '@posthog/core' | ||
| import { createReadStream } from 'node:fs' | ||
| import { ErrorTracking as CoreErrorTracking, type Logger } from '@posthog/core' | ||
| import { constants, type ReadStream } from 'node:fs' | ||
| import { open, type FileHandle } from 'node:fs/promises' | ||
| import { isAbsolute } from 'node:path' | ||
| import { createInterface } from 'node:readline' | ||
@@ -18,7 +20,11 @@ | ||
| export const MAX_CONTEXTLINES_LINENO: number = 10000 | ||
| export const MAX_CONTEXTLINES_FILE_SIZE: number = 10 * 1024 * 1024 | ||
| type ReadlineRange = [start: number, end: number] | ||
| type OpenSourceFile = (path: string, flags: number) => Promise<FileHandle> | ||
| export async function addSourceContext( | ||
| frames: CoreErrorTracking.StackFrame[] | ||
| frames: CoreErrorTracking.StackFrame[], | ||
| openSourceFile: OpenSourceFile = open, | ||
| logger?: Pick<Logger, 'debug'> | ||
| ): Promise<CoreErrorTracking.StackFrame[]> { | ||
@@ -28,2 +34,8 @@ // keep a lookup map of which files we've already enqueued to read, | ||
| const filesToLines: Record<string, number[]> = {} | ||
| let basePath: string | undefined | ||
| try { | ||
| basePath = process.cwd() | ||
| } catch { | ||
| // Absolute source paths can still be processed when the working directory was removed. | ||
| } | ||
@@ -46,2 +58,5 @@ // Maps preserve insertion order, so we iterate in reverse, starting at the | ||
| if (!isAbsolute(filename) && basePath === undefined) { | ||
| continue | ||
| } | ||
| const filesToLinesOutput = filesToLines[filename] | ||
@@ -61,4 +76,9 @@ if (!filesToLinesOutput) { | ||
| for (const file of files) { | ||
| const cacheKey = makeSourceCacheKey(file, basePath) | ||
| if (cacheKey === undefined) { | ||
| continue | ||
| } | ||
| // If we failed to read this before, dont try reading it again. | ||
| if (LRU_FILE_CONTENTS_FS_READ_FAILED.get(file)) { | ||
| if (LRU_FILE_CONTENTS_FS_READ_FAILED.get(cacheKey)) { | ||
| continue | ||
@@ -76,8 +96,8 @@ } | ||
| const ranges = makeLineReaderRanges(filesToLineRanges) | ||
| if (ranges.every((r) => rangeExistsInContentCache(file, r))) { | ||
| if (ranges.every((r) => rangeExistsInContentCache(cacheKey, r))) { | ||
| continue | ||
| } | ||
| const cache = emplace(LRU_FILE_CONTENTS_CACHE, file, {}) | ||
| readlinePromises.push(getContextLinesFromFile(file, ranges, cache)) | ||
| const cache = emplace(LRU_FILE_CONTENTS_CACHE, cacheKey, {}) | ||
| readlinePromises.push(getContextLinesFromFile(file, ranges, cache, cacheKey, openSourceFile, logger)) | ||
| } | ||
@@ -91,3 +111,3 @@ | ||
| if (frames && frames.length > 0) { | ||
| addSourceContextToFrames(frames, LRU_FILE_CONTENTS_CACHE) | ||
| addSourceContextToFrames(frames, LRU_FILE_CONTENTS_CACHE, basePath) | ||
| } | ||
@@ -103,20 +123,94 @@ | ||
| /** | ||
| * Opens a bounded regular file without blocking on special files such as FIFOs. | ||
| * File validation and content reads use the same descriptor so path replacement cannot change what is read. | ||
| */ | ||
| async function openRegularSourceFile( | ||
| path: string, | ||
| openSourceFile: OpenSourceFile, | ||
| logger?: Pick<Logger, 'debug'> | ||
| ): Promise<FileHandle | undefined> { | ||
| let fileHandle: FileHandle | undefined | ||
| let isValid = false | ||
| try { | ||
| fileHandle = await openSourceFile(path, constants.O_RDONLY | constants.O_NONBLOCK) | ||
| const fileStat = await fileHandle.stat() | ||
| if (!fileStat.isFile()) { | ||
| return undefined | ||
| } | ||
| if (fileStat.size > MAX_CONTEXTLINES_FILE_SIZE) { | ||
| logger?.debug( | ||
| `Skipping source context for oversized file ${path}: ${fileStat.size} bytes exceeds ${MAX_CONTEXTLINES_FILE_SIZE}` | ||
| ) | ||
| return undefined | ||
| } | ||
| isValid = true | ||
| return fileHandle | ||
| } catch { | ||
| return undefined | ||
| } finally { | ||
| if (fileHandle && !isValid) { | ||
| await fileHandle.close().catch(() => {}) | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * Extracts lines from a file and stores them in a cache. | ||
| */ | ||
| function getContextLinesFromFile(path: string, ranges: ReadlineRange[], output: Record<number, string>): Promise<void> { | ||
| async function getContextLinesFromFile( | ||
| path: string, | ||
| ranges: ReadlineRange[], | ||
| output: Record<number, string>, | ||
| cacheKey: string, | ||
| openSourceFile: OpenSourceFile, | ||
| logger?: Pick<Logger, 'debug'> | ||
| ): Promise<void> { | ||
| const fileHandle = await openRegularSourceFile(path, openSourceFile, logger) | ||
| if (fileHandle === undefined) { | ||
| LRU_FILE_CONTENTS_FS_READ_FAILED.set(cacheKey, 1) | ||
| return | ||
| } | ||
| const openedFileHandle = fileHandle | ||
| return new Promise((resolve) => { | ||
| let finished = false | ||
| // We need to explicitly destroy the stream and close its descriptor to prevent memory leaks, | ||
| // removing the listeners on the readline interface is not enough. | ||
| // Otherwise, repeated exception captures can keep opening the same files without closing them. | ||
| function destroyStreamAndResolve(stream?: ReadStream): void { | ||
| if (finished) { | ||
| return | ||
| } | ||
| finished = true | ||
| stream?.destroy() | ||
| void openedFileHandle.close().then(resolve, resolve) | ||
| } | ||
| // It is important *not* to have any async code between createInterface and the 'line' event listener | ||
| // as it will cause the 'line' event to | ||
| // be emitted before the listener is attached. | ||
| const stream = createReadStream(path) | ||
| const lineReaded = createInterface({ | ||
| input: stream, | ||
| }) | ||
| let stream: ReadStream | ||
| try { | ||
| stream = openedFileHandle.createReadStream({ | ||
| autoClose: false, | ||
| start: 0, | ||
| end: MAX_CONTEXTLINES_FILE_SIZE - 1, | ||
| }) | ||
| } catch { | ||
| LRU_FILE_CONTENTS_FS_READ_FAILED.set(cacheKey, 1) | ||
| destroyStreamAndResolve() | ||
| return | ||
| } | ||
| // We need to explicitly destroy the stream to prevent memory leaks, | ||
| // removing the listeners on the readline interface is not enough. | ||
| // Otherwise, repeated exception captures can keep opening the same files without closing them. | ||
| function destroyStreamAndResolve(): void { | ||
| stream.destroy() | ||
| resolve() | ||
| let lineReaded: ReturnType<typeof createInterface> | ||
| try { | ||
| lineReaded = createInterface({ | ||
| input: stream, | ||
| }) | ||
| } catch { | ||
| LRU_FILE_CONTENTS_FS_READ_FAILED.set(cacheKey, 1) | ||
| destroyStreamAndResolve(stream) | ||
| return | ||
| } | ||
@@ -130,3 +224,3 @@ | ||
| // We should never reach this point, but if we do, we should resolve the promise to prevent it from hanging. | ||
| destroyStreamAndResolve() | ||
| destroyStreamAndResolve(stream) | ||
| return | ||
@@ -140,7 +234,7 @@ } | ||
| function onStreamError(): void { | ||
| // Mark file path as failed to read and prevent multiple read attempts. | ||
| LRU_FILE_CONTENTS_FS_READ_FAILED.set(path, 1) | ||
| // Mark the file as failed to prevent repeated read attempts. | ||
| LRU_FILE_CONTENTS_FS_READ_FAILED.set(cacheKey, 1) | ||
| lineReaded.close() | ||
| lineReaded.removeAllListeners() | ||
| destroyStreamAndResolve() | ||
| destroyStreamAndResolve(stream) | ||
| } | ||
@@ -152,3 +246,3 @@ | ||
| lineReaded.on('error', onStreamError) | ||
| lineReaded.on('close', destroyStreamAndResolve) | ||
| lineReaded.on('close', () => destroyStreamAndResolve(stream)) | ||
@@ -189,3 +283,4 @@ lineReaded.on('line', (line) => { | ||
| frames: CoreErrorTracking.StackFrame[], | ||
| cache: CoreErrorTracking.ReduceableCache<string, Record<number, string>> | ||
| cache: CoreErrorTracking.ReduceableCache<string, Record<number, string>>, | ||
| basePath: string | undefined | ||
| ): void { | ||
@@ -195,3 +290,4 @@ for (const frame of frames) { | ||
| if (frame.filename && frame.context_line === undefined && typeof frame.lineno === 'number') { | ||
| const contents = cache.get(frame.filename) | ||
| const cacheKey = makeSourceCacheKey(frame.filename, basePath) | ||
| const contents = cacheKey === undefined ? undefined : cache.get(cacheKey) | ||
| if (contents === undefined) { | ||
@@ -296,7 +392,14 @@ continue | ||
| function makeSourceCacheKey(path: string, basePath: string | undefined): string | undefined { | ||
| if (isAbsolute(path)) { | ||
| return JSON.stringify([null, path]) | ||
| } | ||
| return basePath === undefined ? undefined : JSON.stringify([basePath, path]) | ||
| } | ||
| /** | ||
| * Checks if we have all the contents that we need in the cache. | ||
| */ | ||
| function rangeExistsInContentCache(file: string, range: ReadlineRange): boolean { | ||
| const contents = LRU_FILE_CONTENTS_CACHE.get(file) | ||
| function rangeExistsInContentCache(cacheKey: string, range: ReadlineRange): boolean { | ||
| const contents = LRU_FILE_CONTENTS_CACHE.get(cacheKey) | ||
| if (contents === undefined) { | ||
@@ -303,0 +406,0 @@ return false |
+1
-1
@@ -1,1 +0,1 @@ | ||
| export const version = '5.47.6' | ||
| export const version = '5.47.7' |
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 2 instances
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
793310
1.17%17501
1.33%14
40%