@@ -17,8 +17,12 @@ import chalk from 'chalk';
17
17
18
18
// @ts -expect-error - magicast is ESM and TS complains about that. It works though
19
19
import { generateCode , loadFile , writeFile } from 'magicast' ;
20
- import { debug } from 'console' ;
21
- import { hasSentryContent } from '../../utils/ast-utils' ;
20
+ import { debug } from '../../utils/debug' ;
21
+ import {
22
+ hasSentryContent ,
23
+ safeCalleeIdentifierMatch ,
24
+ safeGetIdentifierName ,
25
+ } from '../../utils/ast-utils' ;
22
26
import { getAfterImportsInsertionIndex } from './utils' ;
23
27
24
28
export async function instrumentServerEntry (
@@ -41,7 +45,6 @@ export async function instrumentServerEntry(
41
45
}
42
46
43
47
export function instrumentHandleRequest (
44
- // MagicAst returns `ProxifiedModule<any>` so therefore we have to use `any` here
45
48
originalEntryServerMod : ProxifiedModule < any > ,
46
49
) : void {
47
50
const originalEntryServerModAST = originalEntryServerMod . $ast as t . Program ;
@@ -59,7 +62,6 @@ export function instrumentHandleRequest(
59
62
) } in your server entry file. Creating one for you.`,
60
63
) ;
61
64
62
- // Add imports required by handleRequest [ServerRouter, renderToPipeableStream, createReadableStreamFromReadable] if not present
63
65
let foundServerRouterImport = false ;
64
66
let foundRenderToPipeableStreamImport = false ;
65
67
let foundCreateReadableStreamFromReadableImport = false ;
@@ -141,7 +143,6 @@ export function instrumentHandleRequest(
141
143
} else {
142
144
let defaultExportNode : recast . types . namedTypes . ExportDefaultDeclaration | null =
143
145
null ;
144
- // Replace the existing default export with the wrapped one
145
146
const defaultExportIndex = originalEntryServerModAST . body . findIndex (
146
147
( node ) => {
147
148
const found = node . type === 'ExportDefaultDeclaration' ;
@@ -155,16 +156,14 @@ export function instrumentHandleRequest(
155
156
) ;
156
157
157
158
if ( defaultExportIndex !== - 1 && defaultExportNode !== null ) {
158
- // Try to find `pipe(body)` so we can wrap the body with `Sentry.getMetaTagTransformer`
159
159
recast . visit ( defaultExportNode , {
160
160
visitCallExpression ( path ) {
161
161
if (
162
- path . value . callee . name === 'pipe' &&
162
+ safeCalleeIdentifierMatch ( path . value . callee , 'pipe' ) &&
163
163
path . value . arguments . length &&
164
164
path . value . arguments [ 0 ] . type === 'Identifier' &&
165
- path . value . arguments [ 0 ] . name === 'body'
165
+ safeGetIdentifierName ( path . value . arguments [ 0 ] ) === 'body'
166
166
) {
167
- // // Wrap the call expression with `Sentry.getMetaTagTransformer`
168
167
const wrapped = recast . types . builders . callExpression (
169
168
recast . types . builders . memberExpression (
170
169
recast . types . builders . identifier ( 'Sentry' ) ,
@@ -220,13 +219,27 @@ export function instrumentHandleError(
220
219
) ;
221
220
222
221
const handleErrorFunctionVariableDeclarationExport =
223
- originalEntryServerModAST . body . find (
224
- ( node ) =>
225
- node . type === 'ExportNamedDeclaration' &&
226
- node . declaration ?. type === 'VariableDeclaration' &&
227
- // @ts -expect-error - id should always have a name in this case
228
- node . declaration . declarations [ 0 ] . id . name === 'handleError' ,
229
- ) ;
222
+ originalEntryServerModAST . body . find ( ( node ) => {
223
+ if (
224
+ node . type !== 'ExportNamedDeclaration' ||
225
+ node . declaration ?. type !== 'VariableDeclaration'
226
+ ) {
227
+ return false ;
228
+ }
229
+
230
+ const declarations = node . declaration . declarations ;
231
+ if ( ! declarations || declarations . length === 0 ) {
232
+ return false ;
233
+ }
234
+
235
+ const firstDeclaration = declarations [ 0 ] ;
236
+ if ( ! firstDeclaration || firstDeclaration . type !== 'VariableDeclarator' ) {
237
+ return false ;
238
+ }
239
+
240
+ const id = firstDeclaration . id ;
241
+ return id && id . type === 'Identifier' && id . name === 'handleError' ;
242
+ } ) ;
230
243
231
244
if (
232
245
! handleErrorFunctionExport &&
@@ -277,25 +290,61 @@ export function instrumentHandleError(
277
290
) {
278
291
debug ( 'createSentryHandleError is already used, skipping adding it again' ) ;
279
292
} else if ( handleErrorFunctionExport ) {
280
- const implementation = recast . parse ( `if (!request.signal.aborted) {
293
+ // Create the Sentry captureException call as an IfStatement
294
+ const sentryCall = recast . parse ( `if (!request.signal.aborted) {
281
295
Sentry.captureException(error);
282
296
}` ) . program . body [ 0 ] ;
283
- // If the current handleError function has a body, we need to merge the new implementation with the existing one
284
- implementation . declarations [ 0 ] . init . arguments [ 0 ] . body . body . unshift (
285
- // @ts -expect-error - declaration works here because the AST is proxified by magicast
286
- ...handleErrorFunctionExport . declaration . body . body ,
287
- ) ;
288
297
298
+ // Safely insert the Sentry call at the beginning of the handleError function body
289
299
// @ts -expect-error - declaration works here because the AST is proxified by magicast
290
- handleErrorFunctionExport . declaration = implementation ;
300
+ const declaration = handleErrorFunctionExport . declaration ;
301
+ if (
302
+ declaration &&
303
+ declaration . body &&
304
+ declaration . body . body &&
305
+ Array . isArray ( declaration . body . body )
306
+ ) {
307
+ declaration . body . body . unshift ( sentryCall ) ;
308
+ } else {
309
+ debug (
310
+ 'Cannot safely access handleError function body, skipping instrumentation' ,
311
+ ) ;
312
+ }
291
313
} else if ( handleErrorFunctionVariableDeclarationExport ) {
292
- const implementation = recast . parse ( `if (!request.signal.aborted) {
314
+ // Create the Sentry captureException call as an IfStatement
315
+ const sentryCall = recast . parse ( `if (!request.signal.aborted) {
293
316
Sentry.captureException(error);
294
317
}` ) . program . body [ 0 ] ;
295
- const existingHandleErrorImplementation =
296
- // @ts -expect-error - declaration works here because the AST is proxified by magicast
297
- handleErrorFunctionVariableDeclarationExport . declaration . declarations [ 0 ]
298
- . init ;
318
+
319
+ // Safe access to existing handle error implementation with proper null checks
320
+ // We know this is ExportNamedDeclaration with VariableDeclaration from the earlier find
321
+ const exportDeclaration =
322
+ handleErrorFunctionVariableDeclarationExport as any ;
323
+ if (
324
+ ! exportDeclaration . declaration ||
325
+ exportDeclaration . declaration . type !== 'VariableDeclaration' ||
326
+ ! exportDeclaration . declaration . declarations ||
327
+ exportDeclaration . declaration . declarations . length === 0
328
+ ) {
329
+ debug (
330
+ 'Cannot safely access handleError variable declaration, skipping instrumentation' ,
331
+ ) ;
332
+ return ;
333
+ }
334
+
335
+ const firstDeclaration = exportDeclaration . declaration . declarations [ 0 ] ;
336
+ if (
337
+ ! firstDeclaration ||
338
+ firstDeclaration . type !== 'VariableDeclarator' ||
339
+ ! firstDeclaration . init
340
+ ) {
341
+ debug (
342
+ 'Cannot safely access handleError variable declarator init, skipping instrumentation' ,
343
+ ) ;
344
+ return ;
345
+ }
346
+
347
+ const existingHandleErrorImplementation = firstDeclaration . init ;
299
348
const existingParams = existingHandleErrorImplementation . params ;
300
349
const existingBody = existingHandleErrorImplementation . body ;
301
350
@@ -322,12 +371,13 @@ export function instrumentHandleError(
322
371
existingParams [ 1 ] . type === 'ObjectPattern' &&
323
372
! existingParams [ 1 ] . properties . some (
324
373
( prop : t . ObjectProperty ) =>
325
- prop . key . type === 'Identifier' && prop . key . name === 'request' ,
374
+ safeGetIdentifierName ( prop . key ) === 'request' ,
326
375
)
327
376
) {
328
377
existingParams [ 1 ] . properties . push ( requestParam ) ;
329
378
}
330
379
331
- existingBody . body . push ( implementation ) ;
380
+ // Add the Sentry call to the function body
381
+ existingBody . body . push ( sentryCall ) ;
332
382
}
333
383
}
0 commit comments