@sentry/wizard 6.6.1 → 6.7.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.
- package/CHANGELOG.md +22 -0
- package/LICENSE +97 -8
- package/dist/bin.js +5 -0
- package/dist/bin.js.map +1 -1
- package/dist/e2e-tests/tests/help-message.test.js +5 -1
- package/dist/e2e-tests/tests/help-message.test.js.map +1 -1
- package/dist/e2e-tests/tests/nextjs-15.test.js +79 -0
- package/dist/e2e-tests/tests/nextjs-15.test.js.map +1 -1
- package/dist/e2e-tests/tests/react-router.test.d.ts +1 -0
- package/dist/e2e-tests/tests/react-router.test.js +255 -0
- package/dist/e2e-tests/tests/react-router.test.js.map +1 -0
- package/dist/e2e-tests/utils/index.d.ts +8 -2
- package/dist/e2e-tests/utils/index.js +72 -21
- package/dist/e2e-tests/utils/index.js.map +1 -1
- package/dist/lib/Constants.d.ts +1 -0
- package/dist/lib/Constants.js +5 -0
- package/dist/lib/Constants.js.map +1 -1
- package/dist/src/android/android-wizard.js +8 -1
- package/dist/src/android/android-wizard.js.map +1 -1
- package/dist/src/angular/angular-wizard.js +8 -1
- package/dist/src/angular/angular-wizard.js.map +1 -1
- package/dist/src/apple/apple-wizard.js +8 -1
- package/dist/src/apple/apple-wizard.js.map +1 -1
- package/dist/src/flutter/flutter-wizard.js +8 -1
- package/dist/src/flutter/flutter-wizard.js.map +1 -1
- package/dist/src/nextjs/nextjs-wizard.js +35 -9
- package/dist/src/nextjs/nextjs-wizard.js.map +1 -1
- package/dist/src/nextjs/templates.d.ts +3 -3
- package/dist/src/nextjs/templates.js +18 -7
- package/dist/src/nextjs/templates.js.map +1 -1
- package/dist/src/nuxt/nuxt-wizard.js +8 -1
- package/dist/src/nuxt/nuxt-wizard.js.map +1 -1
- package/dist/src/react-native/react-native-wizard.js +8 -1
- package/dist/src/react-native/react-native-wizard.js.map +1 -1
- package/dist/src/react-router/codemods/client.entry.d.ts +1 -0
- package/dist/src/react-router/codemods/client.entry.js +73 -0
- package/dist/src/react-router/codemods/client.entry.js.map +1 -0
- package/dist/src/react-router/codemods/react-router-config.d.ts +9 -0
- package/dist/src/react-router/codemods/react-router-config.js +178 -0
- package/dist/src/react-router/codemods/react-router-config.js.map +1 -0
- package/dist/src/react-router/codemods/root.d.ts +1 -0
- package/dist/src/react-router/codemods/root.js +171 -0
- package/dist/src/react-router/codemods/root.js.map +1 -0
- package/dist/src/react-router/codemods/routes-config.d.ts +1 -0
- package/dist/src/react-router/codemods/routes-config.js +106 -0
- package/dist/src/react-router/codemods/routes-config.js.map +1 -0
- package/dist/src/react-router/codemods/server-entry.d.ts +4 -0
- package/dist/src/react-router/codemods/server-entry.js +275 -0
- package/dist/src/react-router/codemods/server-entry.js.map +1 -0
- package/dist/src/react-router/codemods/utils.d.ts +2 -0
- package/dist/src/react-router/codemods/utils.js +13 -0
- package/dist/src/react-router/codemods/utils.js.map +1 -0
- package/dist/src/react-router/codemods/vite.d.ts +8 -0
- package/dist/src/react-router/codemods/vite.js +169 -0
- package/dist/src/react-router/codemods/vite.js.map +1 -0
- package/dist/src/react-router/react-router-wizard.d.ts +2 -0
- package/dist/src/react-router/react-router-wizard.js +254 -0
- package/dist/src/react-router/react-router-wizard.js.map +1 -0
- package/dist/src/react-router/sdk-example.d.ts +18 -0
- package/dist/src/react-router/sdk-example.js +306 -0
- package/dist/src/react-router/sdk-example.js.map +1 -0
- package/dist/src/react-router/sdk-setup.d.ts +17 -0
- package/dist/src/react-router/sdk-setup.js +250 -0
- package/dist/src/react-router/sdk-setup.js.map +1 -0
- package/dist/src/react-router/templates.d.ts +11 -0
- package/dist/src/react-router/templates.js +273 -0
- package/dist/src/react-router/templates.js.map +1 -0
- package/dist/src/remix/remix-wizard.js +8 -1
- package/dist/src/remix/remix-wizard.js.map +1 -1
- package/dist/src/run.d.ts +2 -1
- package/dist/src/run.js +6 -0
- package/dist/src/run.js.map +1 -1
- package/dist/src/sourcemaps/sourcemaps-wizard.js +8 -1
- package/dist/src/sourcemaps/sourcemaps-wizard.js.map +1 -1
- package/dist/src/sveltekit/sveltekit-wizard.js +8 -1
- package/dist/src/sveltekit/sveltekit-wizard.js.map +1 -1
- package/dist/src/utils/ast-utils.d.ts +30 -0
- package/dist/src/utils/ast-utils.js +71 -1
- package/dist/src/utils/ast-utils.js.map +1 -1
- package/dist/src/utils/clack/index.d.ts +5 -2
- package/dist/src/utils/clack/index.js +8 -0
- package/dist/src/utils/clack/index.js.map +1 -1
- package/dist/src/utils/types.d.ts +9 -0
- package/dist/src/utils/types.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/version.js.map +1 -1
- package/dist/test/nextjs/templates.test.js +20 -0
- package/dist/test/nextjs/templates.test.js.map +1 -1
- package/dist/test/react-router/codemods/client-entry.test.d.ts +1 -0
- package/dist/test/react-router/codemods/client-entry.test.js +168 -0
- package/dist/test/react-router/codemods/client-entry.test.js.map +1 -0
- package/dist/test/react-router/codemods/react-router-config.test.d.ts +1 -0
- package/dist/test/react-router/codemods/react-router-config.test.js +168 -0
- package/dist/test/react-router/codemods/react-router-config.test.js.map +1 -0
- package/dist/test/react-router/codemods/root.test.d.ts +1 -0
- package/dist/test/react-router/codemods/root.test.js +178 -0
- package/dist/test/react-router/codemods/root.test.js.map +1 -0
- package/dist/test/react-router/codemods/server-entry.test.d.ts +1 -0
- package/dist/test/react-router/codemods/server-entry.test.js +415 -0
- package/dist/test/react-router/codemods/server-entry.test.js.map +1 -0
- package/dist/test/react-router/codemods/vite.test.d.ts +1 -0
- package/dist/test/react-router/codemods/vite.test.js +158 -0
- package/dist/test/react-router/codemods/vite.test.js.map +1 -0
- package/dist/test/react-router/routes-config.test.d.ts +1 -0
- package/dist/test/react-router/routes-config.test.js +156 -0
- package/dist/test/react-router/routes-config.test.js.map +1 -0
- package/dist/test/react-router/sdk-setup.test.d.ts +1 -0
- package/dist/test/react-router/sdk-setup.test.js +411 -0
- package/dist/test/react-router/sdk-setup.test.js.map +1 -0
- package/dist/test/react-router/templates.test.d.ts +1 -0
- package/dist/test/react-router/templates.test.js +220 -0
- package/dist/test/react-router/templates.test.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.instrumentRoot = void 0;
|
|
27
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
28
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
29
|
+
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
30
|
+
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
31
|
+
const recast = __importStar(require("recast"));
|
|
32
|
+
const path = __importStar(require("path"));
|
|
33
|
+
const magicast_1 = require("magicast");
|
|
34
|
+
const templates_1 = require("../templates");
|
|
35
|
+
const ast_utils_1 = require("../../utils/ast-utils");
|
|
36
|
+
const debug_1 = require("../../utils/debug");
|
|
37
|
+
function hasCaptureExceptionCall(node) {
|
|
38
|
+
let found = false;
|
|
39
|
+
recast.visit(node, {
|
|
40
|
+
visitCallExpression(path) {
|
|
41
|
+
const callee = path.value.callee;
|
|
42
|
+
if ((callee.type === 'MemberExpression' &&
|
|
43
|
+
callee.object?.name === 'Sentry' &&
|
|
44
|
+
callee.property?.name === 'captureException') ||
|
|
45
|
+
(callee.type === 'Identifier' && callee.name === 'captureException')) {
|
|
46
|
+
found = true;
|
|
47
|
+
}
|
|
48
|
+
this.traverse(path);
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
return found;
|
|
52
|
+
}
|
|
53
|
+
function addCaptureExceptionCall(functionNode) {
|
|
54
|
+
const captureExceptionCall = recast.parse(`Sentry.captureException(error);`)
|
|
55
|
+
.program.body[0];
|
|
56
|
+
const functionBody = (0, ast_utils_1.safeGetFunctionBody)(functionNode);
|
|
57
|
+
if (functionBody) {
|
|
58
|
+
if (!(0, ast_utils_1.safeInsertBeforeReturn)(functionBody, captureExceptionCall)) {
|
|
59
|
+
functionBody.push(captureExceptionCall);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
(0, debug_1.debug)('Could not safely access ErrorBoundary function body');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function findErrorBoundaryInExports(namedExports) {
|
|
67
|
+
return namedExports.some((namedExport) => {
|
|
68
|
+
const declaration = namedExport.declaration;
|
|
69
|
+
if (!declaration) {
|
|
70
|
+
return namedExport.specifiers?.some((spec) => spec.type === 'ExportSpecifier' &&
|
|
71
|
+
spec.exported?.type === 'Identifier' &&
|
|
72
|
+
spec.exported.name === 'ErrorBoundary');
|
|
73
|
+
}
|
|
74
|
+
if (declaration.type === 'FunctionDeclaration') {
|
|
75
|
+
return declaration.id?.name === 'ErrorBoundary';
|
|
76
|
+
}
|
|
77
|
+
if (declaration.type === 'VariableDeclaration') {
|
|
78
|
+
return declaration.declarations.some((decl) => {
|
|
79
|
+
// @ts-expect-error - id should always have a name in this case
|
|
80
|
+
return decl.id?.name === 'ErrorBoundary';
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
async function instrumentRoot(rootFileName) {
|
|
87
|
+
const filePath = path.join(process.cwd(), 'app', rootFileName);
|
|
88
|
+
const rootRouteAst = await (0, magicast_1.loadFile)(filePath);
|
|
89
|
+
const exportsAst = rootRouteAst.exports.$ast;
|
|
90
|
+
const namedExports = exportsAst.body.filter((node) => node.type === 'ExportNamedDeclaration');
|
|
91
|
+
const foundErrorBoundary = findErrorBoundaryInExports(namedExports);
|
|
92
|
+
const alreadyHasSentry = (0, ast_utils_1.hasSentryContent)(rootRouteAst.$ast);
|
|
93
|
+
if (!alreadyHasSentry) {
|
|
94
|
+
rootRouteAst.imports.$add({
|
|
95
|
+
from: '@sentry/react-router',
|
|
96
|
+
imported: '*',
|
|
97
|
+
local: 'Sentry',
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if (!foundErrorBoundary) {
|
|
101
|
+
const hasIsRouteErrorResponseImport = rootRouteAst.imports.$items.some((item) => item.imported === 'isRouteErrorResponse' &&
|
|
102
|
+
item.from === 'react-router');
|
|
103
|
+
if (!hasIsRouteErrorResponseImport) {
|
|
104
|
+
rootRouteAst.imports.$add({
|
|
105
|
+
from: 'react-router',
|
|
106
|
+
imported: 'isRouteErrorResponse',
|
|
107
|
+
local: 'isRouteErrorResponse',
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
recast.visit(rootRouteAst.$ast, {
|
|
111
|
+
visitExportDefaultDeclaration(path) {
|
|
112
|
+
const implementation = recast.parse(templates_1.ERROR_BOUNDARY_TEMPLATE).program
|
|
113
|
+
.body[0];
|
|
114
|
+
path.insertBefore(recast.types.builders.exportDeclaration(false, implementation));
|
|
115
|
+
this.traverse(path);
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
recast.visit(rootRouteAst.$ast, {
|
|
121
|
+
visitExportNamedDeclaration(path) {
|
|
122
|
+
const declaration = path.value.declaration;
|
|
123
|
+
if (!declaration) {
|
|
124
|
+
this.traverse(path);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
let functionToInstrument = null;
|
|
128
|
+
if (declaration.type === 'FunctionDeclaration' &&
|
|
129
|
+
declaration.id?.name === 'ErrorBoundary') {
|
|
130
|
+
functionToInstrument = declaration;
|
|
131
|
+
}
|
|
132
|
+
else if (declaration.type === 'VariableDeclaration' &&
|
|
133
|
+
declaration.declarations?.[0]?.id?.name === 'ErrorBoundary') {
|
|
134
|
+
const init = declaration.declarations[0].init;
|
|
135
|
+
if (init &&
|
|
136
|
+
(init.type === 'FunctionExpression' ||
|
|
137
|
+
init.type === 'ArrowFunctionExpression')) {
|
|
138
|
+
functionToInstrument = init;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (functionToInstrument &&
|
|
142
|
+
!hasCaptureExceptionCall(functionToInstrument)) {
|
|
143
|
+
addCaptureExceptionCall(functionToInstrument);
|
|
144
|
+
}
|
|
145
|
+
this.traverse(path);
|
|
146
|
+
},
|
|
147
|
+
visitVariableDeclaration(path) {
|
|
148
|
+
if (path.value.declarations?.[0]?.id?.name === 'ErrorBoundary') {
|
|
149
|
+
const init = path.value.declarations[0].init;
|
|
150
|
+
if (init &&
|
|
151
|
+
(init.type === 'FunctionExpression' ||
|
|
152
|
+
init.type === 'ArrowFunctionExpression') &&
|
|
153
|
+
!hasCaptureExceptionCall(init)) {
|
|
154
|
+
addCaptureExceptionCall(init);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
this.traverse(path);
|
|
158
|
+
},
|
|
159
|
+
visitFunctionDeclaration(path) {
|
|
160
|
+
if (path.value.id?.name === 'ErrorBoundary' &&
|
|
161
|
+
!hasCaptureExceptionCall(path.value)) {
|
|
162
|
+
addCaptureExceptionCall(path.value);
|
|
163
|
+
}
|
|
164
|
+
this.traverse(path);
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
await (0, magicast_1.writeFile)(rootRouteAst.$ast, filePath);
|
|
169
|
+
}
|
|
170
|
+
exports.instrumentRoot = instrumentRoot;
|
|
171
|
+
//# sourceMappingURL=root.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"root.js","sourceRoot":"","sources":["../../../../src/react-router/codemods/root.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint-disable @typescript-eslint/no-unsafe-member-access */\n/* eslint-disable @typescript-eslint/no-unsafe-assignment */\n/* eslint-disable @typescript-eslint/no-unsafe-call */\n/* eslint-disable @typescript-eslint/no-unsafe-argument */\nimport * as recast from 'recast';\nimport * as path from 'path';\n\nimport type { ExportNamedDeclaration } from '@babel/types';\nimport type { namedTypes as t } from 'ast-types';\n\nimport {\n loadFile,\n writeFile,\n // @ts-expect-error - magicast is ESM and TS complains about that. It works though\n} from 'magicast';\n\nimport { ERROR_BOUNDARY_TEMPLATE } from '../templates';\nimport {\n hasSentryContent,\n safeGetFunctionBody,\n safeInsertBeforeReturn,\n} from '../../utils/ast-utils';\nimport { debug } from '../../utils/debug';\n\nfunction hasCaptureExceptionCall(node: t.Node): boolean {\n let found = false;\n recast.visit(node, {\n visitCallExpression(path) {\n const callee = path.value.callee;\n if (\n (callee.type === 'MemberExpression' &&\n callee.object?.name === 'Sentry' &&\n callee.property?.name === 'captureException') ||\n (callee.type === 'Identifier' && callee.name === 'captureException')\n ) {\n found = true;\n }\n this.traverse(path);\n },\n });\n return found;\n}\n\nfunction addCaptureExceptionCall(functionNode: t.Node): void {\n const captureExceptionCall = recast.parse(`Sentry.captureException(error);`)\n .program.body[0];\n\n const functionBody = safeGetFunctionBody(functionNode);\n if (functionBody) {\n if (!safeInsertBeforeReturn(functionBody, captureExceptionCall)) {\n functionBody.push(captureExceptionCall);\n }\n } else {\n debug('Could not safely access ErrorBoundary function body');\n }\n}\n\nfunction findErrorBoundaryInExports(\n namedExports: ExportNamedDeclaration[],\n): boolean {\n return namedExports.some((namedExport) => {\n const declaration = namedExport.declaration;\n\n if (!declaration) {\n return namedExport.specifiers?.some(\n (spec) =>\n spec.type === 'ExportSpecifier' &&\n spec.exported?.type === 'Identifier' &&\n spec.exported.name === 'ErrorBoundary',\n );\n }\n\n if (declaration.type === 'FunctionDeclaration') {\n return declaration.id?.name === 'ErrorBoundary';\n }\n\n if (declaration.type === 'VariableDeclaration') {\n return declaration.declarations.some((decl) => {\n // @ts-expect-error - id should always have a name in this case\n return decl.id?.name === 'ErrorBoundary';\n });\n }\n\n return false;\n });\n}\n\nexport async function instrumentRoot(rootFileName: string): Promise<void> {\n const filePath = path.join(process.cwd(), 'app', rootFileName);\n const rootRouteAst = await loadFile(filePath);\n\n const exportsAst = rootRouteAst.exports.$ast as t.Program;\n const namedExports = exportsAst.body.filter(\n (node) => node.type === 'ExportNamedDeclaration',\n ) as ExportNamedDeclaration[];\n\n const foundErrorBoundary = findErrorBoundaryInExports(namedExports);\n const alreadyHasSentry = hasSentryContent(rootRouteAst.$ast as t.Program);\n\n if (!alreadyHasSentry) {\n rootRouteAst.imports.$add({\n from: '@sentry/react-router',\n imported: '*',\n local: 'Sentry',\n });\n }\n\n if (!foundErrorBoundary) {\n const hasIsRouteErrorResponseImport = rootRouteAst.imports.$items.some(\n (item) =>\n item.imported === 'isRouteErrorResponse' &&\n item.from === 'react-router',\n );\n\n if (!hasIsRouteErrorResponseImport) {\n rootRouteAst.imports.$add({\n from: 'react-router',\n imported: 'isRouteErrorResponse',\n local: 'isRouteErrorResponse',\n });\n }\n\n recast.visit(rootRouteAst.$ast, {\n visitExportDefaultDeclaration(path) {\n const implementation = recast.parse(ERROR_BOUNDARY_TEMPLATE).program\n .body[0];\n\n path.insertBefore(\n recast.types.builders.exportDeclaration(false, implementation),\n );\n\n this.traverse(path);\n },\n });\n } else {\n recast.visit(rootRouteAst.$ast, {\n visitExportNamedDeclaration(path) {\n const declaration = path.value.declaration;\n if (!declaration) {\n this.traverse(path);\n return;\n }\n\n let functionToInstrument = null;\n\n if (\n declaration.type === 'FunctionDeclaration' &&\n declaration.id?.name === 'ErrorBoundary'\n ) {\n functionToInstrument = declaration;\n } else if (\n declaration.type === 'VariableDeclaration' &&\n declaration.declarations?.[0]?.id?.name === 'ErrorBoundary'\n ) {\n const init = declaration.declarations[0].init;\n if (\n init &&\n (init.type === 'FunctionExpression' ||\n init.type === 'ArrowFunctionExpression')\n ) {\n functionToInstrument = init;\n }\n }\n\n if (\n functionToInstrument &&\n !hasCaptureExceptionCall(functionToInstrument)\n ) {\n addCaptureExceptionCall(functionToInstrument);\n }\n\n this.traverse(path);\n },\n\n visitVariableDeclaration(path) {\n if (path.value.declarations?.[0]?.id?.name === 'ErrorBoundary') {\n const init = path.value.declarations[0].init;\n if (\n init &&\n (init.type === 'FunctionExpression' ||\n init.type === 'ArrowFunctionExpression') &&\n !hasCaptureExceptionCall(init)\n ) {\n addCaptureExceptionCall(init);\n }\n }\n this.traverse(path);\n },\n\n visitFunctionDeclaration(path) {\n if (\n path.value.id?.name === 'ErrorBoundary' &&\n !hasCaptureExceptionCall(path.value)\n ) {\n addCaptureExceptionCall(path.value);\n }\n this.traverse(path);\n },\n });\n }\n\n await writeFile(rootRouteAst.$ast, filePath);\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function addRoutesToConfig(routesConfigPath: string, isTS: boolean): Promise<void>;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
4
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
5
|
+
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
23
|
+
if (mod && mod.__esModule) return mod;
|
|
24
|
+
var result = {};
|
|
25
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
26
|
+
__setModuleDefault(result, mod);
|
|
27
|
+
return result;
|
|
28
|
+
};
|
|
29
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
+
exports.addRoutesToConfig = void 0;
|
|
31
|
+
const recast = __importStar(require("recast"));
|
|
32
|
+
const fs = __importStar(require("fs"));
|
|
33
|
+
// @ts-expect-error - magicast is ESM and TS complains about that. It works though
|
|
34
|
+
const magicast_1 = require("magicast");
|
|
35
|
+
async function addRoutesToConfig(routesConfigPath, isTS) {
|
|
36
|
+
// Check if file exists first
|
|
37
|
+
if (!fs.existsSync(routesConfigPath)) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const routesAst = await (0, magicast_1.loadFile)(routesConfigPath);
|
|
41
|
+
// Check if routes are already added
|
|
42
|
+
const routesCode = routesAst.$code;
|
|
43
|
+
if (routesCode.includes('sentry-example-page') &&
|
|
44
|
+
routesCode.includes('sentry-example-api')) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
// Add route import if not already present
|
|
48
|
+
const hasRouteImport = routesAst.imports.$items.some(
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
50
|
+
(item) => item.imported === 'route' && item.from === '@react-router/dev/routes');
|
|
51
|
+
if (!hasRouteImport) {
|
|
52
|
+
routesAst.imports.$add({
|
|
53
|
+
from: '@react-router/dev/routes',
|
|
54
|
+
imported: 'route',
|
|
55
|
+
local: 'route',
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
// Set up the new routes
|
|
59
|
+
const routeExtension = isTS ? 'tsx' : 'jsx';
|
|
60
|
+
const apiExtension = isTS ? 'ts' : 'js';
|
|
61
|
+
const pageRouteCode = `route("/sentry-example-page", "routes/sentry-example-page.${routeExtension}")`;
|
|
62
|
+
const apiRouteCode = `route("/api/sentry-example-api", "routes/api.sentry-example-api.${apiExtension}")`;
|
|
63
|
+
let foundDefaultExport = false;
|
|
64
|
+
// Get the AST program
|
|
65
|
+
const program = routesAst.$ast;
|
|
66
|
+
// Find the default export
|
|
67
|
+
for (let i = 0; i < program.body.length; i++) {
|
|
68
|
+
const node = program.body[i];
|
|
69
|
+
if (node.type === 'ExportDefaultDeclaration') {
|
|
70
|
+
foundDefaultExport = true;
|
|
71
|
+
const declaration = node.declaration;
|
|
72
|
+
let arrayExpression = null;
|
|
73
|
+
if (declaration && declaration.type === 'ArrayExpression') {
|
|
74
|
+
arrayExpression = declaration;
|
|
75
|
+
}
|
|
76
|
+
else if (declaration && declaration.type === 'TSSatisfiesExpression') {
|
|
77
|
+
// Handle TypeScript satisfies expression like: [...] satisfies RouteConfig
|
|
78
|
+
if (declaration.expression &&
|
|
79
|
+
declaration.expression.type === 'ArrayExpression') {
|
|
80
|
+
arrayExpression = declaration.expression;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (arrayExpression) {
|
|
84
|
+
// Parse and add the new route calls directly to the elements array
|
|
85
|
+
const pageRouteCall = recast.parse(pageRouteCode).program.body[0].expression;
|
|
86
|
+
const apiRouteCall = recast.parse(apiRouteCode).program.body[0].expression;
|
|
87
|
+
arrayExpression.elements.push(pageRouteCall);
|
|
88
|
+
arrayExpression.elements.push(apiRouteCall);
|
|
89
|
+
}
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// If no default export found, add one
|
|
94
|
+
if (!foundDefaultExport) {
|
|
95
|
+
// Create a simple array export without satisfies for now
|
|
96
|
+
const newExportCode = `export default [
|
|
97
|
+
${pageRouteCode},
|
|
98
|
+
${apiRouteCode},
|
|
99
|
+
];`;
|
|
100
|
+
const newExport = recast.parse(newExportCode).program.body[0];
|
|
101
|
+
program.body.push(newExport);
|
|
102
|
+
}
|
|
103
|
+
await (0, magicast_1.writeFile)(routesAst.$ast, routesConfigPath);
|
|
104
|
+
}
|
|
105
|
+
exports.addRoutesToConfig = addRoutesToConfig;
|
|
106
|
+
//# sourceMappingURL=routes-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes-config.js","sourceRoot":"","sources":["../../../../src/react-router/codemods/routes-config.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint-disable @typescript-eslint/no-unsafe-argument */\n/* eslint-disable @typescript-eslint/no-unsafe-member-access */\n/* eslint-disable @typescript-eslint/no-unsafe-assignment */\n/* eslint-disable @typescript-eslint/no-unsafe-call */\n\nimport * as recast from 'recast';\nimport * as fs from 'fs';\nimport type { namedTypes as t } from 'ast-types';\n\n// @ts-expect-error - magicast is ESM and TS complains about that. It works though\nimport { loadFile, writeFile } from 'magicast';\n\nexport async function addRoutesToConfig(\n routesConfigPath: string,\n isTS: boolean,\n): Promise<void> {\n // Check if file exists first\n if (!fs.existsSync(routesConfigPath)) {\n return;\n }\n\n const routesAst = await loadFile(routesConfigPath);\n\n // Check if routes are already added\n const routesCode = routesAst.$code;\n if (\n routesCode.includes('sentry-example-page') &&\n routesCode.includes('sentry-example-api')\n ) {\n return;\n }\n\n // Add route import if not already present\n const hasRouteImport = routesAst.imports.$items.some(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (item: any) =>\n item.imported === 'route' && item.from === '@react-router/dev/routes',\n );\n\n if (!hasRouteImport) {\n routesAst.imports.$add({\n from: '@react-router/dev/routes',\n imported: 'route',\n local: 'route',\n });\n }\n\n // Set up the new routes\n const routeExtension = isTS ? 'tsx' : 'jsx';\n const apiExtension = isTS ? 'ts' : 'js';\n\n const pageRouteCode = `route(\"/sentry-example-page\", \"routes/sentry-example-page.${routeExtension}\")`;\n const apiRouteCode = `route(\"/api/sentry-example-api\", \"routes/api.sentry-example-api.${apiExtension}\")`;\n\n let foundDefaultExport = false;\n\n // Get the AST program\n const program = routesAst.$ast as t.Program;\n\n // Find the default export\n for (let i = 0; i < program.body.length; i++) {\n const node = program.body[i];\n\n if (node.type === 'ExportDefaultDeclaration') {\n foundDefaultExport = true;\n\n const declaration = node.declaration;\n\n let arrayExpression = null;\n\n if (declaration && declaration.type === 'ArrayExpression') {\n arrayExpression = declaration;\n } else if (declaration && declaration.type === 'TSSatisfiesExpression') {\n // Handle TypeScript satisfies expression like: [...] satisfies RouteConfig\n if (\n declaration.expression &&\n declaration.expression.type === 'ArrayExpression'\n ) {\n arrayExpression = declaration.expression;\n }\n }\n\n if (arrayExpression) {\n // Parse and add the new route calls directly to the elements array\n const pageRouteCall =\n recast.parse(pageRouteCode).program.body[0].expression;\n const apiRouteCall =\n recast.parse(apiRouteCode).program.body[0].expression;\n\n arrayExpression.elements.push(pageRouteCall);\n arrayExpression.elements.push(apiRouteCall);\n }\n break;\n }\n }\n\n // If no default export found, add one\n if (!foundDefaultExport) {\n // Create a simple array export without satisfies for now\n const newExportCode = `export default [\n ${pageRouteCode},\n ${apiRouteCode},\n];`;\n\n const newExport = recast.parse(newExportCode).program.body[0];\n program.body.push(newExport);\n }\n\n await writeFile(routesAst.$ast, routesConfigPath);\n}\n"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ProxifiedModule } from 'magicast';
|
|
2
|
+
export declare function instrumentServerEntry(serverEntryPath: string): Promise<void>;
|
|
3
|
+
export declare function instrumentHandleRequest(originalEntryServerMod: ProxifiedModule<any>): void;
|
|
4
|
+
export declare function instrumentHandleError(originalEntryServerMod: ProxifiedModule<any>): void;
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
4
|
+
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
5
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
6
|
+
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
7
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
25
|
+
if (mod && mod.__esModule) return mod;
|
|
26
|
+
var result = {};
|
|
27
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
28
|
+
__setModuleDefault(result, mod);
|
|
29
|
+
return result;
|
|
30
|
+
};
|
|
31
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
32
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
33
|
+
};
|
|
34
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
|
+
exports.instrumentHandleError = exports.instrumentHandleRequest = exports.instrumentServerEntry = void 0;
|
|
36
|
+
const recast = __importStar(require("recast"));
|
|
37
|
+
// @ts-expect-error - clack is ESM and TS complains about that. It works though
|
|
38
|
+
const prompts_1 = __importDefault(require("@clack/prompts"));
|
|
39
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
40
|
+
// @ts-expect-error - magicast is ESM and TS complains about that. It works though
|
|
41
|
+
const magicast_1 = require("magicast");
|
|
42
|
+
const debug_1 = require("../../utils/debug");
|
|
43
|
+
const ast_utils_1 = require("../../utils/ast-utils");
|
|
44
|
+
const utils_1 = require("./utils");
|
|
45
|
+
async function instrumentServerEntry(serverEntryPath) {
|
|
46
|
+
const serverEntryAst = await (0, magicast_1.loadFile)(serverEntryPath);
|
|
47
|
+
if (!(0, ast_utils_1.hasSentryContent)(serverEntryAst.$ast)) {
|
|
48
|
+
serverEntryAst.imports.$add({
|
|
49
|
+
from: '@sentry/react-router',
|
|
50
|
+
imported: '*',
|
|
51
|
+
local: 'Sentry',
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
instrumentHandleError(serverEntryAst);
|
|
55
|
+
instrumentHandleRequest(serverEntryAst);
|
|
56
|
+
await (0, magicast_1.writeFile)(serverEntryAst.$ast, serverEntryPath);
|
|
57
|
+
}
|
|
58
|
+
exports.instrumentServerEntry = instrumentServerEntry;
|
|
59
|
+
function instrumentHandleRequest(originalEntryServerMod) {
|
|
60
|
+
const originalEntryServerModAST = originalEntryServerMod.$ast;
|
|
61
|
+
const defaultServerEntryExport = originalEntryServerModAST.body.find((node) => {
|
|
62
|
+
return node.type === 'ExportDefaultDeclaration';
|
|
63
|
+
});
|
|
64
|
+
if (!defaultServerEntryExport) {
|
|
65
|
+
prompts_1.default.log.warn(`Could not find function ${chalk_1.default.cyan('handleRequest')} in your server entry file. Creating one for you.`);
|
|
66
|
+
let foundServerRouterImport = false;
|
|
67
|
+
let foundRenderToPipeableStreamImport = false;
|
|
68
|
+
let foundCreateReadableStreamFromReadableImport = false;
|
|
69
|
+
originalEntryServerMod.imports.$items.forEach((item) => {
|
|
70
|
+
if (item.imported === 'ServerRouter' && item.from === 'react-router') {
|
|
71
|
+
foundServerRouterImport = true;
|
|
72
|
+
}
|
|
73
|
+
if (item.imported === 'renderToPipeableStream' &&
|
|
74
|
+
item.from === 'react-dom/server') {
|
|
75
|
+
foundRenderToPipeableStreamImport = true;
|
|
76
|
+
}
|
|
77
|
+
if (item.imported === 'createReadableStreamFromReadable' &&
|
|
78
|
+
item.from === '@react-router/node') {
|
|
79
|
+
foundCreateReadableStreamFromReadableImport = true;
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
if (!foundServerRouterImport) {
|
|
83
|
+
originalEntryServerMod.imports.$add({
|
|
84
|
+
from: 'react-router',
|
|
85
|
+
imported: 'ServerRouter',
|
|
86
|
+
local: 'ServerRouter',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
if (!foundRenderToPipeableStreamImport) {
|
|
90
|
+
originalEntryServerMod.imports.$add({
|
|
91
|
+
from: 'react-dom/server',
|
|
92
|
+
imported: 'renderToPipeableStream',
|
|
93
|
+
local: 'renderToPipeableStream',
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
if (!foundCreateReadableStreamFromReadableImport) {
|
|
97
|
+
originalEntryServerMod.imports.$add({
|
|
98
|
+
from: '@react-router/node',
|
|
99
|
+
imported: 'createReadableStreamFromReadable',
|
|
100
|
+
local: 'createReadableStreamFromReadable',
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
const implementation = recast.parse(`const handleRequest = Sentry.createSentryHandleRequest({
|
|
104
|
+
ServerRouter,
|
|
105
|
+
renderToPipeableStream,
|
|
106
|
+
createReadableStreamFromReadable,
|
|
107
|
+
})`).program.body[0];
|
|
108
|
+
try {
|
|
109
|
+
originalEntryServerModAST.body.splice((0, utils_1.getAfterImportsInsertionIndex)(originalEntryServerModAST), 0, implementation);
|
|
110
|
+
originalEntryServerModAST.body.push({
|
|
111
|
+
type: 'ExportDefaultDeclaration',
|
|
112
|
+
declaration: {
|
|
113
|
+
type: 'Identifier',
|
|
114
|
+
name: 'handleRequest',
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
(0, debug_1.debug)('Failed to insert handleRequest implementation:', error);
|
|
120
|
+
throw new Error('Could not automatically instrument handleRequest. Please add it manually.');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
else if (defaultServerEntryExport &&
|
|
124
|
+
// @ts-expect-error - StatementKind works here because the AST is proxified by magicast
|
|
125
|
+
(0, magicast_1.generateCode)(defaultServerEntryExport).code.includes('wrapSentryHandleRequest')) {
|
|
126
|
+
(0, debug_1.debug)('wrapSentryHandleRequest is already used, skipping wrapping again');
|
|
127
|
+
prompts_1.default.log.info('Sentry handleRequest wrapper already detected, skipping instrumentation.');
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
let defaultExportNode = null;
|
|
131
|
+
const defaultExportIndex = originalEntryServerModAST.body.findIndex((node) => {
|
|
132
|
+
const found = node.type === 'ExportDefaultDeclaration';
|
|
133
|
+
if (found) {
|
|
134
|
+
defaultExportNode = node;
|
|
135
|
+
}
|
|
136
|
+
return found;
|
|
137
|
+
});
|
|
138
|
+
if (defaultExportIndex !== -1 && defaultExportNode !== null) {
|
|
139
|
+
recast.visit(defaultExportNode, {
|
|
140
|
+
visitCallExpression(path) {
|
|
141
|
+
if ((0, ast_utils_1.safeCalleeIdentifierMatch)(path.value.callee, 'pipe') &&
|
|
142
|
+
path.value.arguments.length &&
|
|
143
|
+
path.value.arguments[0].type === 'Identifier' &&
|
|
144
|
+
(0, ast_utils_1.safeGetIdentifierName)(path.value.arguments[0]) === 'body') {
|
|
145
|
+
const wrapped = recast.types.builders.callExpression(recast.types.builders.memberExpression(recast.types.builders.identifier('Sentry'), recast.types.builders.identifier('getMetaTagTransformer')), [path.value.arguments[0]]);
|
|
146
|
+
path.value.arguments[0] = wrapped;
|
|
147
|
+
}
|
|
148
|
+
this.traverse(path);
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
// Replace the existing default export with the wrapped one
|
|
152
|
+
originalEntryServerModAST.body.splice(defaultExportIndex, 1,
|
|
153
|
+
// @ts-expect-error - declaration works here because the AST is proxified by magicast
|
|
154
|
+
defaultExportNode.declaration);
|
|
155
|
+
// Adding our wrapped export
|
|
156
|
+
originalEntryServerModAST.body.push(recast.types.builders.exportDefaultDeclaration(recast.types.builders.callExpression(recast.types.builders.memberExpression(recast.types.builders.identifier('Sentry'), recast.types.builders.identifier('wrapSentryHandleRequest')), [recast.types.builders.identifier('handleRequest')])));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
exports.instrumentHandleRequest = instrumentHandleRequest;
|
|
161
|
+
function instrumentHandleError(originalEntryServerMod) {
|
|
162
|
+
const originalEntryServerModAST = originalEntryServerMod.$ast;
|
|
163
|
+
const handleErrorFunctionExport = originalEntryServerModAST.body.find((node) => {
|
|
164
|
+
return (node.type === 'ExportNamedDeclaration' &&
|
|
165
|
+
node.declaration?.type === 'FunctionDeclaration' &&
|
|
166
|
+
node.declaration.id?.name === 'handleError');
|
|
167
|
+
});
|
|
168
|
+
const handleErrorFunctionVariableDeclarationExport = originalEntryServerModAST.body.find((node) => {
|
|
169
|
+
if (node.type !== 'ExportNamedDeclaration' ||
|
|
170
|
+
node.declaration?.type !== 'VariableDeclaration') {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
const declarations = node.declaration.declarations;
|
|
174
|
+
if (!declarations || declarations.length === 0) {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
const firstDeclaration = declarations[0];
|
|
178
|
+
if (!firstDeclaration || firstDeclaration.type !== 'VariableDeclarator') {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
const id = firstDeclaration.id;
|
|
182
|
+
return id && id.type === 'Identifier' && id.name === 'handleError';
|
|
183
|
+
});
|
|
184
|
+
if (!handleErrorFunctionExport &&
|
|
185
|
+
!handleErrorFunctionVariableDeclarationExport) {
|
|
186
|
+
prompts_1.default.log.warn(`Could not find function ${chalk_1.default.cyan('handleError')} in your server entry file. Creating one for you.`);
|
|
187
|
+
const implementation = recast.parse(`const handleError = Sentry.createSentryHandleError({
|
|
188
|
+
logErrors: false
|
|
189
|
+
})`).program.body[0];
|
|
190
|
+
originalEntryServerModAST.body.splice((0, utils_1.getAfterImportsInsertionIndex)(originalEntryServerModAST), 0, recast.types.builders.exportNamedDeclaration(implementation));
|
|
191
|
+
}
|
|
192
|
+
else if ((handleErrorFunctionExport &&
|
|
193
|
+
// @ts-expect-error - StatementKind works here because the AST is proxified by magicast
|
|
194
|
+
(0, magicast_1.generateCode)(handleErrorFunctionExport).code.includes('captureException')) ||
|
|
195
|
+
(handleErrorFunctionVariableDeclarationExport &&
|
|
196
|
+
// @ts-expect-error - StatementKind works here because the AST is proxified by magicast
|
|
197
|
+
(0, magicast_1.generateCode)(handleErrorFunctionVariableDeclarationExport).code.includes('captureException'))) {
|
|
198
|
+
(0, debug_1.debug)('Found captureException inside handleError, skipping adding it again');
|
|
199
|
+
}
|
|
200
|
+
else if ((handleErrorFunctionExport &&
|
|
201
|
+
// @ts-expect-error - StatementKind works here because the AST is proxified by magicast
|
|
202
|
+
(0, magicast_1.generateCode)(handleErrorFunctionExport).code.includes('createSentryHandleError')) ||
|
|
203
|
+
(handleErrorFunctionVariableDeclarationExport &&
|
|
204
|
+
// @ts-expect-error - StatementKind works here because the AST is proxified by magicast
|
|
205
|
+
(0, magicast_1.generateCode)(handleErrorFunctionVariableDeclarationExport).code.includes('createSentryHandleError'))) {
|
|
206
|
+
(0, debug_1.debug)('createSentryHandleError is already used, skipping adding it again');
|
|
207
|
+
}
|
|
208
|
+
else if (handleErrorFunctionExport) {
|
|
209
|
+
// Create the Sentry captureException call as an IfStatement
|
|
210
|
+
const sentryCall = recast.parse(`if (!request.signal.aborted) {
|
|
211
|
+
Sentry.captureException(error);
|
|
212
|
+
}`).program.body[0];
|
|
213
|
+
// Safely insert the Sentry call at the beginning of the handleError function body
|
|
214
|
+
// @ts-expect-error - declaration works here because the AST is proxified by magicast
|
|
215
|
+
const declaration = handleErrorFunctionExport.declaration;
|
|
216
|
+
if (declaration &&
|
|
217
|
+
declaration.body &&
|
|
218
|
+
declaration.body.body &&
|
|
219
|
+
Array.isArray(declaration.body.body)) {
|
|
220
|
+
declaration.body.body.unshift(sentryCall);
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
(0, debug_1.debug)('Cannot safely access handleError function body, skipping instrumentation');
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
else if (handleErrorFunctionVariableDeclarationExport) {
|
|
227
|
+
// Create the Sentry captureException call as an IfStatement
|
|
228
|
+
const sentryCall = recast.parse(`if (!request.signal.aborted) {
|
|
229
|
+
Sentry.captureException(error);
|
|
230
|
+
}`).program.body[0];
|
|
231
|
+
// Safe access to existing handle error implementation with proper null checks
|
|
232
|
+
// We know this is ExportNamedDeclaration with VariableDeclaration from the earlier find
|
|
233
|
+
const exportDeclaration = handleErrorFunctionVariableDeclarationExport;
|
|
234
|
+
if (!exportDeclaration.declaration ||
|
|
235
|
+
exportDeclaration.declaration.type !== 'VariableDeclaration' ||
|
|
236
|
+
!exportDeclaration.declaration.declarations ||
|
|
237
|
+
exportDeclaration.declaration.declarations.length === 0) {
|
|
238
|
+
(0, debug_1.debug)('Cannot safely access handleError variable declaration, skipping instrumentation');
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const firstDeclaration = exportDeclaration.declaration.declarations[0];
|
|
242
|
+
if (!firstDeclaration ||
|
|
243
|
+
firstDeclaration.type !== 'VariableDeclarator' ||
|
|
244
|
+
!firstDeclaration.init) {
|
|
245
|
+
(0, debug_1.debug)('Cannot safely access handleError variable declarator init, skipping instrumentation');
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const existingHandleErrorImplementation = firstDeclaration.init;
|
|
249
|
+
const existingParams = existingHandleErrorImplementation.params;
|
|
250
|
+
const existingBody = existingHandleErrorImplementation.body;
|
|
251
|
+
const requestParam = {
|
|
252
|
+
...recast.types.builders.property('init', recast.types.builders.identifier('request'), // key
|
|
253
|
+
recast.types.builders.identifier('request')),
|
|
254
|
+
shorthand: true,
|
|
255
|
+
};
|
|
256
|
+
// Add error and {request} parameters to handleError function if not present
|
|
257
|
+
// When none of the parameters exist
|
|
258
|
+
if (existingParams.length === 0) {
|
|
259
|
+
existingParams.push(recast.types.builders.identifier('error'), recast.types.builders.objectPattern([requestParam]));
|
|
260
|
+
// When only error parameter exists
|
|
261
|
+
}
|
|
262
|
+
else if (existingParams.length === 1) {
|
|
263
|
+
existingParams.push(recast.types.builders.objectPattern([requestParam]));
|
|
264
|
+
// When both parameters exist, but request is not destructured
|
|
265
|
+
}
|
|
266
|
+
else if (existingParams[1].type === 'ObjectPattern' &&
|
|
267
|
+
!existingParams[1].properties.some((prop) => (0, ast_utils_1.safeGetIdentifierName)(prop.key) === 'request')) {
|
|
268
|
+
existingParams[1].properties.push(requestParam);
|
|
269
|
+
}
|
|
270
|
+
// Add the Sentry call to the function body
|
|
271
|
+
existingBody.body.push(sentryCall);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
exports.instrumentHandleError = instrumentHandleError;
|
|
275
|
+
//# sourceMappingURL=server-entry.js.map
|