@sentry/wizard 6.6.1 → 6.8.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 +28 -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/pnpm-workspace.test.d.ts +1 -0
- package/dist/e2e-tests/tests/pnpm-workspace.test.js +206 -0
- package/dist/e2e-tests/tests/pnpm-workspace.test.js.map +1 -0
- 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/package-json.js +86 -2
- package/dist/src/utils/package-json.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/dist/test/utils/package-json.test.d.ts +1 -0
- package/dist/test/utils/package-json.test.js +428 -0
- package/dist/test/utils/package-json.test.js.map +1 -0
- package/package.json +4 -2
|
@@ -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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-entry.js","sourceRoot":"","sources":["../../../../src/react-router/codemods/server-entry.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint-disable @typescript-eslint/no-unsafe-argument */\n/* eslint-disable @typescript-eslint/no-explicit-any */\n/* eslint-disable @typescript-eslint/no-unsafe-call */\n/* eslint-disable @typescript-eslint/no-unsafe-member-access */\n/* eslint-disable @typescript-eslint/no-unsafe-return */\n/* eslint-disable @typescript-eslint/no-unsafe-assignment */\n\n// @ts-expect-error - magicast is ESM and TS complains about that. It works though\nimport type { ProxifiedModule } from 'magicast';\n\nimport * as recast from 'recast';\nimport type { namedTypes as t } from 'ast-types';\n\n// @ts-expect-error - clack is ESM and TS complains about that. It works though\nimport clack from '@clack/prompts';\nimport chalk from 'chalk';\n\n// @ts-expect-error - magicast is ESM and TS complains about that. It works though\nimport { generateCode, loadFile, writeFile } from 'magicast';\nimport { debug } from '../../utils/debug';\nimport {\n hasSentryContent,\n safeCalleeIdentifierMatch,\n safeGetIdentifierName,\n} from '../../utils/ast-utils';\nimport { getAfterImportsInsertionIndex } from './utils';\n\nexport async function instrumentServerEntry(\n serverEntryPath: string,\n): Promise<void> {\n const serverEntryAst = await loadFile(serverEntryPath);\n\n if (!hasSentryContent(serverEntryAst.$ast as t.Program)) {\n serverEntryAst.imports.$add({\n from: '@sentry/react-router',\n imported: '*',\n local: 'Sentry',\n });\n }\n\n instrumentHandleError(serverEntryAst);\n instrumentHandleRequest(serverEntryAst);\n\n await writeFile(serverEntryAst.$ast, serverEntryPath);\n}\n\nexport function instrumentHandleRequest(\n originalEntryServerMod: ProxifiedModule<any>,\n): void {\n const originalEntryServerModAST = originalEntryServerMod.$ast as t.Program;\n\n const defaultServerEntryExport = originalEntryServerModAST.body.find(\n (node) => {\n return node.type === 'ExportDefaultDeclaration';\n },\n );\n\n if (!defaultServerEntryExport) {\n clack.log.warn(\n `Could not find function ${chalk.cyan(\n 'handleRequest',\n )} in your server entry file. Creating one for you.`,\n );\n\n let foundServerRouterImport = false;\n let foundRenderToPipeableStreamImport = false;\n let foundCreateReadableStreamFromReadableImport = false;\n\n originalEntryServerMod.imports.$items.forEach((item) => {\n if (item.imported === 'ServerRouter' && item.from === 'react-router') {\n foundServerRouterImport = true;\n }\n if (\n item.imported === 'renderToPipeableStream' &&\n item.from === 'react-dom/server'\n ) {\n foundRenderToPipeableStreamImport = true;\n }\n if (\n item.imported === 'createReadableStreamFromReadable' &&\n item.from === '@react-router/node'\n ) {\n foundCreateReadableStreamFromReadableImport = true;\n }\n });\n\n if (!foundServerRouterImport) {\n originalEntryServerMod.imports.$add({\n from: 'react-router',\n imported: 'ServerRouter',\n local: 'ServerRouter',\n });\n }\n\n if (!foundRenderToPipeableStreamImport) {\n originalEntryServerMod.imports.$add({\n from: 'react-dom/server',\n imported: 'renderToPipeableStream',\n local: 'renderToPipeableStream',\n });\n }\n\n if (!foundCreateReadableStreamFromReadableImport) {\n originalEntryServerMod.imports.$add({\n from: '@react-router/node',\n imported: 'createReadableStreamFromReadable',\n local: 'createReadableStreamFromReadable',\n });\n }\n\n const implementation =\n recast.parse(`const handleRequest = Sentry.createSentryHandleRequest({\n ServerRouter,\n renderToPipeableStream,\n createReadableStreamFromReadable,\n})`).program.body[0];\n\n try {\n originalEntryServerModAST.body.splice(\n getAfterImportsInsertionIndex(originalEntryServerModAST),\n 0,\n implementation,\n );\n\n originalEntryServerModAST.body.push({\n type: 'ExportDefaultDeclaration',\n declaration: {\n type: 'Identifier',\n name: 'handleRequest',\n },\n });\n } catch (error) {\n debug('Failed to insert handleRequest implementation:', error);\n throw new Error(\n 'Could not automatically instrument handleRequest. Please add it manually.',\n );\n }\n } else if (\n defaultServerEntryExport &&\n // @ts-expect-error - StatementKind works here because the AST is proxified by magicast\n generateCode(defaultServerEntryExport).code.includes(\n 'wrapSentryHandleRequest',\n )\n ) {\n debug('wrapSentryHandleRequest is already used, skipping wrapping again');\n clack.log.info(\n 'Sentry handleRequest wrapper already detected, skipping instrumentation.',\n );\n } else {\n let defaultExportNode: recast.types.namedTypes.ExportDefaultDeclaration | null =\n null;\n const defaultExportIndex = originalEntryServerModAST.body.findIndex(\n (node) => {\n const found = node.type === 'ExportDefaultDeclaration';\n\n if (found) {\n defaultExportNode = node;\n }\n\n return found;\n },\n );\n\n if (defaultExportIndex !== -1 && defaultExportNode !== null) {\n recast.visit(defaultExportNode, {\n visitCallExpression(path) {\n if (\n safeCalleeIdentifierMatch(path.value.callee, 'pipe') &&\n path.value.arguments.length &&\n path.value.arguments[0].type === 'Identifier' &&\n safeGetIdentifierName(path.value.arguments[0]) === 'body'\n ) {\n const wrapped = recast.types.builders.callExpression(\n recast.types.builders.memberExpression(\n recast.types.builders.identifier('Sentry'),\n recast.types.builders.identifier('getMetaTagTransformer'),\n ),\n [path.value.arguments[0]],\n );\n\n path.value.arguments[0] = wrapped;\n }\n\n this.traverse(path);\n },\n });\n\n // Replace the existing default export with the wrapped one\n originalEntryServerModAST.body.splice(\n defaultExportIndex,\n 1,\n // @ts-expect-error - declaration works here because the AST is proxified by magicast\n defaultExportNode.declaration,\n );\n\n // Adding our wrapped export\n originalEntryServerModAST.body.push(\n recast.types.builders.exportDefaultDeclaration(\n recast.types.builders.callExpression(\n recast.types.builders.memberExpression(\n recast.types.builders.identifier('Sentry'),\n recast.types.builders.identifier('wrapSentryHandleRequest'),\n ),\n [recast.types.builders.identifier('handleRequest')],\n ),\n ),\n );\n }\n }\n}\n\nexport function instrumentHandleError(\n originalEntryServerMod: ProxifiedModule<any>,\n): void {\n const originalEntryServerModAST = originalEntryServerMod.$ast as t.Program;\n\n const handleErrorFunctionExport = originalEntryServerModAST.body.find(\n (node) => {\n return (\n node.type === 'ExportNamedDeclaration' &&\n node.declaration?.type === 'FunctionDeclaration' &&\n node.declaration.id?.name === 'handleError'\n );\n },\n );\n\n const handleErrorFunctionVariableDeclarationExport =\n originalEntryServerModAST.body.find((node) => {\n if (\n node.type !== 'ExportNamedDeclaration' ||\n node.declaration?.type !== 'VariableDeclaration'\n ) {\n return false;\n }\n\n const declarations = node.declaration.declarations;\n if (!declarations || declarations.length === 0) {\n return false;\n }\n\n const firstDeclaration = declarations[0];\n if (!firstDeclaration || firstDeclaration.type !== 'VariableDeclarator') {\n return false;\n }\n\n const id = firstDeclaration.id;\n return id && id.type === 'Identifier' && id.name === 'handleError';\n });\n\n if (\n !handleErrorFunctionExport &&\n !handleErrorFunctionVariableDeclarationExport\n ) {\n clack.log.warn(\n `Could not find function ${chalk.cyan(\n 'handleError',\n )} in your server entry file. Creating one for you.`,\n );\n\n const implementation =\n recast.parse(`const handleError = Sentry.createSentryHandleError({\n logErrors: false\n})`).program.body[0];\n\n originalEntryServerModAST.body.splice(\n getAfterImportsInsertionIndex(originalEntryServerModAST),\n 0,\n recast.types.builders.exportNamedDeclaration(implementation),\n );\n } else if (\n (handleErrorFunctionExport &&\n // @ts-expect-error - StatementKind works here because the AST is proxified by magicast\n generateCode(handleErrorFunctionExport).code.includes(\n 'captureException',\n )) ||\n (handleErrorFunctionVariableDeclarationExport &&\n // @ts-expect-error - StatementKind works here because the AST is proxified by magicast\n generateCode(handleErrorFunctionVariableDeclarationExport).code.includes(\n 'captureException',\n ))\n ) {\n debug(\n 'Found captureException inside handleError, skipping adding it again',\n );\n } else if (\n (handleErrorFunctionExport &&\n // @ts-expect-error - StatementKind works here because the AST is proxified by magicast\n generateCode(handleErrorFunctionExport).code.includes(\n 'createSentryHandleError',\n )) ||\n (handleErrorFunctionVariableDeclarationExport &&\n // @ts-expect-error - StatementKind works here because the AST is proxified by magicast\n generateCode(handleErrorFunctionVariableDeclarationExport).code.includes(\n 'createSentryHandleError',\n ))\n ) {\n debug('createSentryHandleError is already used, skipping adding it again');\n } else if (handleErrorFunctionExport) {\n // Create the Sentry captureException call as an IfStatement\n const sentryCall = recast.parse(`if (!request.signal.aborted) {\n Sentry.captureException(error);\n}`).program.body[0];\n\n // Safely insert the Sentry call at the beginning of the handleError function body\n // @ts-expect-error - declaration works here because the AST is proxified by magicast\n const declaration = handleErrorFunctionExport.declaration;\n if (\n declaration &&\n declaration.body &&\n declaration.body.body &&\n Array.isArray(declaration.body.body)\n ) {\n declaration.body.body.unshift(sentryCall);\n } else {\n debug(\n 'Cannot safely access handleError function body, skipping instrumentation',\n );\n }\n } else if (handleErrorFunctionVariableDeclarationExport) {\n // Create the Sentry captureException call as an IfStatement\n const sentryCall = recast.parse(`if (!request.signal.aborted) {\n Sentry.captureException(error);\n}`).program.body[0];\n\n // Safe access to existing handle error implementation with proper null checks\n // We know this is ExportNamedDeclaration with VariableDeclaration from the earlier find\n const exportDeclaration =\n handleErrorFunctionVariableDeclarationExport as any;\n if (\n !exportDeclaration.declaration ||\n exportDeclaration.declaration.type !== 'VariableDeclaration' ||\n !exportDeclaration.declaration.declarations ||\n exportDeclaration.declaration.declarations.length === 0\n ) {\n debug(\n 'Cannot safely access handleError variable declaration, skipping instrumentation',\n );\n return;\n }\n\n const firstDeclaration = exportDeclaration.declaration.declarations[0];\n if (\n !firstDeclaration ||\n firstDeclaration.type !== 'VariableDeclarator' ||\n !firstDeclaration.init\n ) {\n debug(\n 'Cannot safely access handleError variable declarator init, skipping instrumentation',\n );\n return;\n }\n\n const existingHandleErrorImplementation = firstDeclaration.init;\n const existingParams = existingHandleErrorImplementation.params;\n const existingBody = existingHandleErrorImplementation.body;\n\n const requestParam = {\n ...recast.types.builders.property(\n 'init',\n recast.types.builders.identifier('request'), // key\n recast.types.builders.identifier('request'), // value\n ),\n shorthand: true,\n };\n // Add error and {request} parameters to handleError function if not present\n // When none of the parameters exist\n if (existingParams.length === 0) {\n existingParams.push(\n recast.types.builders.identifier('error'),\n recast.types.builders.objectPattern([requestParam]),\n );\n // When only error parameter exists\n } else if (existingParams.length === 1) {\n existingParams.push(recast.types.builders.objectPattern([requestParam]));\n // When both parameters exist, but request is not destructured\n } else if (\n existingParams[1].type === 'ObjectPattern' &&\n !existingParams[1].properties.some(\n (prop: t.ObjectProperty) =>\n safeGetIdentifierName(prop.key) === 'request',\n )\n ) {\n existingParams[1].properties.push(requestParam);\n }\n\n // Add the Sentry call to the function body\n existingBody.body.push(sentryCall);\n }\n}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getAfterImportsInsertionIndex = void 0;
|
|
4
|
+
function getAfterImportsInsertionIndex(originalEntryServerModAST) {
|
|
5
|
+
for (let x = originalEntryServerModAST.body.length - 1; x >= 0; x--) {
|
|
6
|
+
if (originalEntryServerModAST.body[x].type === 'ImportDeclaration') {
|
|
7
|
+
return x + 1;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
return 0;
|
|
11
|
+
}
|
|
12
|
+
exports.getAfterImportsInsertionIndex = getAfterImportsInsertionIndex;
|
|
13
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../../src/react-router/codemods/utils.ts"],"names":[],"mappings":";;;AAEA,SAAgB,6BAA6B,CAC3C,yBAAoC;IAEpC,KAAK,IAAI,CAAC,GAAG,yBAAyB,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE;QACnE,IAAI,yBAAyB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,mBAAmB,EAAE;YAClE,OAAO,CAAC,GAAG,CAAC,CAAC;SACd;KACF;IAED,OAAO,CAAC,CAAC;AACX,CAAC;AAVD,sEAUC","sourcesContent":["import type { namedTypes as t } from 'ast-types';\n\nexport function getAfterImportsInsertionIndex(\n originalEntryServerModAST: t.Program,\n): number {\n for (let x = originalEntryServerModAST.body.length - 1; x >= 0; x--) {\n if (originalEntryServerModAST.body[x].type === 'ImportDeclaration') {\n return x + 1;\n }\n }\n\n return 0;\n}\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { namedTypes as t } from 'ast-types';
|
|
2
|
+
export declare function addReactRouterPluginToViteConfig(program: t.Program, orgSlug: string, projectSlug: string): {
|
|
3
|
+
success: boolean;
|
|
4
|
+
wasConverted: boolean;
|
|
5
|
+
};
|
|
6
|
+
export declare function instrumentViteConfig(orgSlug: string, projectSlug: string): Promise<{
|
|
7
|
+
wasConverted: boolean;
|
|
8
|
+
}>;
|
|
@@ -0,0 +1,169 @@
|
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.instrumentViteConfig = exports.addReactRouterPluginToViteConfig = void 0;
|
|
30
|
+
const recast = __importStar(require("recast"));
|
|
31
|
+
const path = __importStar(require("path"));
|
|
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
|
+
// @ts-expect-error - clack is ESM and TS complains about that. It works though
|
|
36
|
+
const prompts_1 = __importDefault(require("@clack/prompts"));
|
|
37
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
38
|
+
const ast_utils_1 = require("../../utils/ast-utils");
|
|
39
|
+
/**
|
|
40
|
+
* Extracts ObjectExpression from function body.
|
|
41
|
+
* Handles both arrow functions with object returns and block statements with explicit returns.
|
|
42
|
+
*
|
|
43
|
+
* - Arrow with object-return: (config) => ({ ... })
|
|
44
|
+
* - Arrow with block: (config) => { return { ... }; }
|
|
45
|
+
* - Function with block: function(config) { return { ... }; }
|
|
46
|
+
*
|
|
47
|
+
* @param body - The function body to extract from
|
|
48
|
+
* @returns The ObjectExpression if found, undefined otherwise
|
|
49
|
+
*/
|
|
50
|
+
function extractFromFunctionBody(body) {
|
|
51
|
+
if (body.type === 'ObjectExpression') {
|
|
52
|
+
return body;
|
|
53
|
+
}
|
|
54
|
+
if (body.type === 'BlockStatement') {
|
|
55
|
+
const blockBody = body;
|
|
56
|
+
const returnStatement = blockBody.body.find((stmt) => stmt.type === 'ReturnStatement');
|
|
57
|
+
return returnStatement?.argument?.type === 'ObjectExpression'
|
|
58
|
+
? returnStatement.argument
|
|
59
|
+
: undefined;
|
|
60
|
+
}
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Creates the sentryReactRouter Vite plugin call expression.
|
|
65
|
+
*
|
|
66
|
+
* Generates AST for:
|
|
67
|
+
* sentryReactRouter({
|
|
68
|
+
* org: "...",
|
|
69
|
+
* project: "...",
|
|
70
|
+
* authToken: process.env.SENTRY_AUTH_TOKEN
|
|
71
|
+
* }, config)
|
|
72
|
+
*
|
|
73
|
+
* @param orgSlug - Sentry organization slug
|
|
74
|
+
* @param projectSlug - Sentry project slug
|
|
75
|
+
* @returns CallExpression node for the Sentry Vite plugin
|
|
76
|
+
*/
|
|
77
|
+
function createSentryPluginCall(orgSlug, projectSlug) {
|
|
78
|
+
const b = recast.types.builders;
|
|
79
|
+
return b.callExpression(b.identifier('sentryReactRouter'), [
|
|
80
|
+
b.objectExpression([
|
|
81
|
+
b.objectProperty(b.identifier('org'), b.stringLiteral(orgSlug)),
|
|
82
|
+
b.objectProperty(b.identifier('project'), b.stringLiteral(projectSlug)),
|
|
83
|
+
b.objectProperty(b.identifier('authToken'), b.memberExpression(b.memberExpression(b.identifier('process'), b.identifier('env')), b.identifier('SENTRY_AUTH_TOKEN'))),
|
|
84
|
+
]),
|
|
85
|
+
b.identifier('config'),
|
|
86
|
+
]);
|
|
87
|
+
}
|
|
88
|
+
function addReactRouterPluginToViteConfig(program, orgSlug, projectSlug) {
|
|
89
|
+
const b = recast.types.builders;
|
|
90
|
+
let wasConverted = false;
|
|
91
|
+
const defaultExport = program.body.find((node) => node.type === 'ExportDefaultDeclaration');
|
|
92
|
+
if (!defaultExport) {
|
|
93
|
+
return { success: false, wasConverted: false };
|
|
94
|
+
}
|
|
95
|
+
let configObj;
|
|
96
|
+
let defineConfigCall;
|
|
97
|
+
if (defaultExport.declaration.type === 'CallExpression' &&
|
|
98
|
+
defaultExport.declaration.callee.type === 'Identifier' &&
|
|
99
|
+
defaultExport.declaration.callee.name === 'defineConfig') {
|
|
100
|
+
defineConfigCall = defaultExport.declaration;
|
|
101
|
+
// Early exit if not single argument
|
|
102
|
+
if (defineConfigCall.arguments.length !== 1) {
|
|
103
|
+
return { success: false, wasConverted: false };
|
|
104
|
+
}
|
|
105
|
+
const arg = defineConfigCall.arguments[0];
|
|
106
|
+
if (arg.type === 'ObjectExpression') {
|
|
107
|
+
configObj = arg;
|
|
108
|
+
// Convert to function form
|
|
109
|
+
const arrowFunction = b.arrowFunctionExpression([b.identifier('config')], configObj);
|
|
110
|
+
defineConfigCall.arguments[0] = arrowFunction;
|
|
111
|
+
wasConverted = true;
|
|
112
|
+
}
|
|
113
|
+
else if (arg.type === 'ArrowFunctionExpression' ||
|
|
114
|
+
arg.type === 'FunctionExpression') {
|
|
115
|
+
configObj = extractFromFunctionBody(arg.body);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (!configObj) {
|
|
119
|
+
return { success: false, wasConverted };
|
|
120
|
+
}
|
|
121
|
+
const pluginsProp = (0, ast_utils_1.findProperty)(configObj, 'plugins');
|
|
122
|
+
const sentryPluginCall = createSentryPluginCall(orgSlug, projectSlug);
|
|
123
|
+
if (!pluginsProp) {
|
|
124
|
+
configObj.properties.push(b.objectProperty(b.identifier('plugins'), b.arrayExpression([sentryPluginCall])));
|
|
125
|
+
}
|
|
126
|
+
else if (pluginsProp.value.type === 'ArrayExpression' &&
|
|
127
|
+
pluginsProp.type === 'ObjectProperty') {
|
|
128
|
+
const arrayExpr = pluginsProp.value;
|
|
129
|
+
// Defensive: ensure elements array exists
|
|
130
|
+
if (!arrayExpr.elements) {
|
|
131
|
+
arrayExpr.elements = [];
|
|
132
|
+
}
|
|
133
|
+
arrayExpr.elements.push(sentryPluginCall);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
return { success: false, wasConverted };
|
|
137
|
+
}
|
|
138
|
+
return { success: true, wasConverted };
|
|
139
|
+
}
|
|
140
|
+
exports.addReactRouterPluginToViteConfig = addReactRouterPluginToViteConfig;
|
|
141
|
+
async function instrumentViteConfig(orgSlug, projectSlug) {
|
|
142
|
+
const configPath = fs.existsSync(path.join(process.cwd(), 'vite.config.ts'))
|
|
143
|
+
? path.join(process.cwd(), 'vite.config.ts')
|
|
144
|
+
: path.join(process.cwd(), 'vite.config.js');
|
|
145
|
+
if (!fs.existsSync(configPath)) {
|
|
146
|
+
throw new Error('Could not find vite.config.ts or vite.config.js');
|
|
147
|
+
}
|
|
148
|
+
const configContent = await fs.promises.readFile(configPath, 'utf-8');
|
|
149
|
+
const filename = chalk_1.default.cyan(path.basename(configPath));
|
|
150
|
+
const mod = (0, magicast_1.parseModule)(configContent);
|
|
151
|
+
if ((0, ast_utils_1.hasSentryContent)(mod.$ast)) {
|
|
152
|
+
prompts_1.default.log.info(`${filename} already contains sentryReactRouter plugin.`);
|
|
153
|
+
return { wasConverted: false };
|
|
154
|
+
}
|
|
155
|
+
mod.imports.$add({
|
|
156
|
+
from: '@sentry/react-router',
|
|
157
|
+
imported: 'sentryReactRouter',
|
|
158
|
+
local: 'sentryReactRouter',
|
|
159
|
+
});
|
|
160
|
+
const { success, wasConverted } = addReactRouterPluginToViteConfig(mod.$ast, orgSlug, projectSlug);
|
|
161
|
+
if (!success) {
|
|
162
|
+
throw new Error('Failed to modify Vite config structure');
|
|
163
|
+
}
|
|
164
|
+
const code = (0, magicast_1.generateCode)(mod.$ast).code;
|
|
165
|
+
await fs.promises.writeFile(configPath, code);
|
|
166
|
+
return { wasConverted };
|
|
167
|
+
}
|
|
168
|
+
exports.instrumentViteConfig = instrumentViteConfig;
|
|
169
|
+
//# sourceMappingURL=vite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite.js","sourceRoot":"","sources":["../../../../src/react-router/codemods/vite.ts"],"names":[],"mappings":"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","sourcesContent":["import type { namedTypes as t } from 'ast-types';\nimport * as recast from 'recast';\nimport * as path from 'path';\nimport * as fs from 'fs';\n\n// @ts-expect-error - magicast is ESM and TS complains about that. It works though\nimport { parseModule, generateCode } from 'magicast';\n\n// @ts-expect-error - clack is ESM and TS complains about that. It works though\nimport clack from '@clack/prompts';\nimport chalk from 'chalk';\n\nimport { hasSentryContent, findProperty } from '../../utils/ast-utils';\n\n/**\n * Extracts ObjectExpression from function body.\n * Handles both arrow functions with object returns and block statements with explicit returns.\n *\n * - Arrow with object-return: (config) => ({ ... })\n * - Arrow with block: (config) => { return { ... }; }\n * - Function with block: function(config) { return { ... }; }\n *\n * @param body - The function body to extract from\n * @returns The ObjectExpression if found, undefined otherwise\n */\nfunction extractFromFunctionBody(\n body: t.Expression | t.BlockStatement,\n): t.ObjectExpression | undefined {\n if (body.type === 'ObjectExpression') {\n return body as t.ObjectExpression;\n }\n\n if (body.type === 'BlockStatement') {\n const blockBody = body as t.BlockStatement;\n const returnStatement = blockBody.body.find(\n (stmt: t.Statement): stmt is t.ReturnStatement =>\n stmt.type === 'ReturnStatement',\n );\n\n return returnStatement?.argument?.type === 'ObjectExpression'\n ? returnStatement.argument\n : undefined;\n }\n\n return undefined;\n}\n\n/**\n * Creates the sentryReactRouter Vite plugin call expression.\n *\n * Generates AST for:\n * sentryReactRouter({\n * org: \"...\",\n * project: \"...\",\n * authToken: process.env.SENTRY_AUTH_TOKEN\n * }, config)\n *\n * @param orgSlug - Sentry organization slug\n * @param projectSlug - Sentry project slug\n * @returns CallExpression node for the Sentry Vite plugin\n */\nfunction createSentryPluginCall(\n orgSlug: string,\n projectSlug: string,\n): t.CallExpression {\n const b = recast.types.builders;\n return b.callExpression(b.identifier('sentryReactRouter'), [\n b.objectExpression([\n b.objectProperty(b.identifier('org'), b.stringLiteral(orgSlug)),\n b.objectProperty(b.identifier('project'), b.stringLiteral(projectSlug)),\n b.objectProperty(\n b.identifier('authToken'),\n b.memberExpression(\n b.memberExpression(b.identifier('process'), b.identifier('env')),\n b.identifier('SENTRY_AUTH_TOKEN'),\n ),\n ),\n ]),\n b.identifier('config'),\n ]);\n}\n\nexport function addReactRouterPluginToViteConfig(\n program: t.Program,\n orgSlug: string,\n projectSlug: string,\n): { success: boolean; wasConverted: boolean } {\n const b = recast.types.builders;\n let wasConverted = false;\n\n const defaultExport = program.body.find(\n (node) => node.type === 'ExportDefaultDeclaration',\n ) as t.ExportDefaultDeclaration | undefined;\n\n if (!defaultExport) {\n return { success: false, wasConverted: false };\n }\n\n let configObj: t.ObjectExpression | undefined;\n let defineConfigCall: t.CallExpression | undefined;\n\n if (\n defaultExport.declaration.type === 'CallExpression' &&\n defaultExport.declaration.callee.type === 'Identifier' &&\n defaultExport.declaration.callee.name === 'defineConfig'\n ) {\n defineConfigCall = defaultExport.declaration;\n\n // Early exit if not single argument\n if (defineConfigCall.arguments.length !== 1) {\n return { success: false, wasConverted: false };\n }\n\n const arg = defineConfigCall.arguments[0];\n\n if (arg.type === 'ObjectExpression') {\n configObj = arg;\n // Convert to function form\n const arrowFunction = b.arrowFunctionExpression(\n [b.identifier('config')],\n configObj,\n );\n defineConfigCall.arguments[0] = arrowFunction;\n wasConverted = true;\n } else if (\n arg.type === 'ArrowFunctionExpression' ||\n arg.type === 'FunctionExpression'\n ) {\n configObj = extractFromFunctionBody(arg.body);\n }\n }\n\n if (!configObj) {\n return { success: false, wasConverted };\n }\n\n const pluginsProp = findProperty(configObj, 'plugins');\n const sentryPluginCall = createSentryPluginCall(orgSlug, projectSlug);\n\n if (!pluginsProp) {\n configObj.properties.push(\n b.objectProperty(\n b.identifier('plugins'),\n b.arrayExpression([sentryPluginCall]),\n ),\n );\n } else if (\n pluginsProp.value.type === 'ArrayExpression' &&\n pluginsProp.type === 'ObjectProperty'\n ) {\n const arrayExpr = pluginsProp.value;\n // Defensive: ensure elements array exists\n if (!arrayExpr.elements) {\n arrayExpr.elements = [];\n }\n arrayExpr.elements.push(sentryPluginCall);\n } else {\n return { success: false, wasConverted };\n }\n\n return { success: true, wasConverted };\n}\n\nexport async function instrumentViteConfig(\n orgSlug: string,\n projectSlug: string,\n): Promise<{ wasConverted: boolean }> {\n const configPath = fs.existsSync(path.join(process.cwd(), 'vite.config.ts'))\n ? path.join(process.cwd(), 'vite.config.ts')\n : path.join(process.cwd(), 'vite.config.js');\n\n if (!fs.existsSync(configPath)) {\n throw new Error('Could not find vite.config.ts or vite.config.js');\n }\n\n const configContent = await fs.promises.readFile(configPath, 'utf-8');\n const filename = chalk.cyan(path.basename(configPath));\n\n const mod = parseModule(configContent);\n\n if (hasSentryContent(mod.$ast as t.Program)) {\n clack.log.info(`${filename} already contains sentryReactRouter plugin.`);\n return { wasConverted: false };\n }\n\n mod.imports.$add({\n from: '@sentry/react-router',\n imported: 'sentryReactRouter',\n local: 'sentryReactRouter',\n });\n\n const { success, wasConverted } = addReactRouterPluginToViteConfig(\n mod.$ast as t.Program,\n orgSlug,\n projectSlug,\n );\n\n if (!success) {\n throw new Error('Failed to modify Vite config structure');\n }\n\n const code = generateCode(mod.$ast).code;\n await fs.promises.writeFile(configPath, code);\n\n return { wasConverted };\n}\n"]}
|