@sentry/wizard 3.16.5 → 3.17.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 +7 -0
- package/dist/package.json +2 -2
- package/dist/src/apple/cocoapod.js +2 -0
- package/dist/src/apple/cocoapod.js.map +1 -1
- package/dist/src/react-native/metro.d.ts +13 -0
- package/dist/src/react-native/metro.js +398 -0
- package/dist/src/react-native/metro.js.map +1 -0
- package/dist/src/react-native/react-native-wizard.d.ts +2 -0
- package/dist/src/react-native/react-native-wizard.js +139 -38
- package/dist/src/react-native/react-native-wizard.js.map +1 -1
- package/dist/src/react-native/uninstall.js +4 -0
- package/dist/src/react-native/uninstall.js.map +1 -1
- package/dist/src/react-native/xcode.d.ts +7 -3
- package/dist/src/react-native/xcode.js +43 -11
- package/dist/src/react-native/xcode.js.map +1 -1
- package/dist/src/remix/remix-wizard.js +80 -37
- package/dist/src/remix/remix-wizard.js.map +1 -1
- package/dist/src/remix/sdk-setup.d.ts +1 -0
- package/dist/src/remix/sdk-setup.js +21 -1
- package/dist/src/remix/sdk-setup.js.map +1 -1
- package/dist/src/utils/ast-utils.d.ts +14 -0
- package/dist/src/utils/ast-utils.js +49 -1
- package/dist/src/utils/ast-utils.js.map +1 -1
- package/dist/test/react-native/metro.test.d.ts +1 -0
- package/dist/test/react-native/metro.test.js +125 -0
- package/dist/test/react-native/metro.test.js.map +1 -0
- package/dist/test/react-native/xcode.test.js +40 -2
- package/dist/test/react-native/xcode.test.js.map +1 -1
- package/package.json +2 -2
- package/src/apple/cocoapod.ts +2 -0
- package/src/react-native/metro.ts +409 -0
- package/src/react-native/react-native-wizard.ts +103 -7
- package/src/react-native/uninstall.ts +3 -0
- package/src/react-native/xcode.ts +70 -12
- package/src/remix/remix-wizard.ts +51 -15
- package/src/remix/sdk-setup.ts +31 -0
- package/src/utils/ast-utils.ts +52 -0
- package/test/react-native/metro.test.ts +283 -0
- package/test/react-native/xcode.test.ts +76 -3
|
@@ -21,7 +21,10 @@ export function getValidExistingBuildPhases(xcodeProject: any): BuildPhaseMap {
|
|
|
21
21
|
return map;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export function patchBundlePhase(
|
|
24
|
+
export function patchBundlePhase(
|
|
25
|
+
bundlePhase: BuildPhase | undefined,
|
|
26
|
+
patch: (script: string) => string,
|
|
27
|
+
) {
|
|
25
28
|
if (!bundlePhase) {
|
|
26
29
|
clack.log.warn(
|
|
27
30
|
`Could not find ${chalk.cyan(
|
|
@@ -42,9 +45,7 @@ export function patchBundlePhase(bundlePhase: BuildPhase | undefined) {
|
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
const script: string = JSON.parse(bundlePhase.shellScript);
|
|
45
|
-
bundlePhase.shellScript = JSON.stringify(
|
|
46
|
-
addSentryToBundleShellScript(script),
|
|
47
|
-
);
|
|
48
|
+
bundlePhase.shellScript = JSON.stringify(patch(script));
|
|
48
49
|
clack.log.success(
|
|
49
50
|
`Patched Build phase ${chalk.cyan('Bundle React Native code and images')}.`,
|
|
50
51
|
);
|
|
@@ -60,7 +61,10 @@ export function unPatchBundlePhase(bundlePhase: BuildPhase | undefined) {
|
|
|
60
61
|
return;
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
if (
|
|
64
|
+
if (
|
|
65
|
+
!bundlePhase.shellScript.match(/sentry-cli\s+react-native\s+xcode/i) &&
|
|
66
|
+
!bundlePhase.shellScript.includes('sentry-xcode.sh')
|
|
67
|
+
) {
|
|
64
68
|
clack.log.success(
|
|
65
69
|
`Build phase ${chalk.cyan(
|
|
66
70
|
'Bundle React Native code and images',
|
|
@@ -97,6 +101,12 @@ export function removeSentryFromBundleShellScript(script: string): string {
|
|
|
97
101
|
/\.\.\/node_modules\/@sentry\/cli\/bin\/sentry-cli\s+react-native\s+xcode\s+\$REACT_NATIVE_XCODE/i,
|
|
98
102
|
'$REACT_NATIVE_XCODE',
|
|
99
103
|
)
|
|
104
|
+
.replace(
|
|
105
|
+
// eslint-disable-next-line no-useless-escape
|
|
106
|
+
/\"\/bin\/sh.*?sentry-xcode.sh\s+\$REACT_NATIVE_XCODE/i,
|
|
107
|
+
// eslint-disable-next-line no-useless-escape
|
|
108
|
+
'"$REACT_NATIVE_XCODE',
|
|
109
|
+
)
|
|
100
110
|
);
|
|
101
111
|
}
|
|
102
112
|
|
|
@@ -107,10 +117,25 @@ export function findBundlePhase(buildPhases: BuildPhaseMap) {
|
|
|
107
117
|
}
|
|
108
118
|
|
|
109
119
|
export function doesBundlePhaseIncludeSentry(buildPhase: BuildPhase) {
|
|
110
|
-
|
|
120
|
+
const containsSentryCliRNCommand = !!buildPhase.shellScript.match(
|
|
121
|
+
/sentry-cli\s+react-native\s+xcode/i,
|
|
122
|
+
);
|
|
123
|
+
const containsBundledScript =
|
|
124
|
+
buildPhase.shellScript.includes('sentry-xcode.sh');
|
|
125
|
+
return containsSentryCliRNCommand || containsBundledScript;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function addSentryWithBundledScriptsToBundleShellScript(
|
|
129
|
+
script: string,
|
|
130
|
+
): string {
|
|
131
|
+
return script.replace(
|
|
132
|
+
'$REACT_NATIVE_XCODE',
|
|
133
|
+
// eslint-disable-next-line no-useless-escape
|
|
134
|
+
'\\"/bin/sh ../node_modules/@sentry/react-native/scripts/sentry-xcode.sh $REACT_NATIVE_XCODE\\"',
|
|
135
|
+
);
|
|
111
136
|
}
|
|
112
137
|
|
|
113
|
-
export function
|
|
138
|
+
export function addSentryWithCliToBundleShellScript(script: string): string {
|
|
114
139
|
return (
|
|
115
140
|
'export SENTRY_PROPERTIES=sentry.properties\n' +
|
|
116
141
|
'export EXTRA_PACKAGER_ARGS="--sourcemap-output $DERIVED_FILE_DIR/main.jsbundle.map"\n' +
|
|
@@ -124,7 +149,36 @@ export function addSentryToBundleShellScript(script: string): string {
|
|
|
124
149
|
);
|
|
125
150
|
}
|
|
126
151
|
|
|
127
|
-
export function
|
|
152
|
+
export function addDebugFilesUploadPhaseWithBundledScripts(
|
|
153
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
154
|
+
xcodeProject: any,
|
|
155
|
+
{ debugFilesUploadPhaseExists }: { debugFilesUploadPhaseExists: boolean },
|
|
156
|
+
) {
|
|
157
|
+
if (debugFilesUploadPhaseExists) {
|
|
158
|
+
clack.log.warn(
|
|
159
|
+
`Build phase ${chalk.cyan(
|
|
160
|
+
'Upload Debug Symbols to Sentry',
|
|
161
|
+
)} already exists.`,
|
|
162
|
+
);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
xcodeProject.addBuildPhase(
|
|
167
|
+
[],
|
|
168
|
+
'PBXShellScriptBuildPhase',
|
|
169
|
+
'Upload Debug Symbols to Sentry',
|
|
170
|
+
null,
|
|
171
|
+
{
|
|
172
|
+
shellPath: '/bin/sh',
|
|
173
|
+
shellScript: `/bin/sh ../node_modules/@sentry/react-native/scripts/sentry-xcode-debug-files.sh`,
|
|
174
|
+
},
|
|
175
|
+
);
|
|
176
|
+
clack.log.success(
|
|
177
|
+
`Added Build phase ${chalk.cyan('Upload Debug Symbols to Sentry')}.`,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function addDebugFilesUploadPhaseWithCli(
|
|
128
182
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
129
183
|
xcodeProject: any,
|
|
130
184
|
{ debugFilesUploadPhaseExists }: { debugFilesUploadPhaseExists: boolean },
|
|
@@ -204,13 +258,17 @@ export function unPatchDebugFilesUploadPhase(
|
|
|
204
258
|
export function findDebugFilesUploadPhase(
|
|
205
259
|
buildPhasesMap: Record<string, BuildPhase>,
|
|
206
260
|
): [key: string, buildPhase: BuildPhase] | undefined {
|
|
207
|
-
return Object.entries(buildPhasesMap).find(
|
|
208
|
-
|
|
261
|
+
return Object.entries(buildPhasesMap).find(([_, buildPhase]) => {
|
|
262
|
+
const containsCliDebugUpload =
|
|
209
263
|
typeof buildPhase !== 'string' &&
|
|
210
264
|
!!buildPhase.shellScript.match(
|
|
211
265
|
/sentry-cli\s+(upload-dsym|debug-files upload)\b/,
|
|
212
|
-
)
|
|
213
|
-
|
|
266
|
+
);
|
|
267
|
+
const containsBundledDebugUpload =
|
|
268
|
+
typeof buildPhase !== 'string' &&
|
|
269
|
+
buildPhase.shellScript.includes('sentry-xcode-debug-files.sh');
|
|
270
|
+
return containsCliDebugUpload || containsBundledDebugUpload;
|
|
271
|
+
});
|
|
214
272
|
}
|
|
215
273
|
|
|
216
274
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -22,11 +22,14 @@ import {
|
|
|
22
22
|
instrumentRootRoute,
|
|
23
23
|
isRemixV2,
|
|
24
24
|
loadRemixConfig,
|
|
25
|
+
runRemixReveal,
|
|
25
26
|
} from './sdk-setup';
|
|
26
27
|
import { debug } from '../utils/debug';
|
|
27
28
|
import { traceStep, withTelemetry } from '../telemetry';
|
|
28
29
|
import { isHydrogenApp } from './utils';
|
|
29
30
|
import { DEFAULT_URL } from '../../lib/Constants';
|
|
31
|
+
import { findFile } from '../utils/ast-utils';
|
|
32
|
+
import { configureVitePlugin } from '../sourcemaps/tools/vite';
|
|
30
33
|
|
|
31
34
|
export async function runRemixWizard(options: WizardOptions): Promise<void> {
|
|
32
35
|
return withTelemetry(
|
|
@@ -55,7 +58,7 @@ async function runRemixWizardWithTelemetry(
|
|
|
55
58
|
// We expect `@remix-run/dev` to be installed for every Remix project
|
|
56
59
|
await ensurePackageIsInstalled(packageJson, '@remix-run/dev', 'Remix');
|
|
57
60
|
|
|
58
|
-
const { selectedProject, authToken, sentryUrl } =
|
|
61
|
+
const { selectedProject, authToken, sentryUrl, selfHosted } =
|
|
59
62
|
await getOrAskForProjectData(options, 'javascript-remix');
|
|
60
63
|
|
|
61
64
|
await installPackage({
|
|
@@ -67,24 +70,47 @@ async function runRemixWizardWithTelemetry(
|
|
|
67
70
|
|
|
68
71
|
const isTS = isUsingTypeScript();
|
|
69
72
|
const isV2 = isRemixV2(remixConfig, packageJson);
|
|
73
|
+
const viteConfig = findFile('vite.config');
|
|
70
74
|
|
|
71
75
|
await addSentryCliConfig({ authToken }, rcCliSetupConfig);
|
|
72
76
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
77
|
+
if (viteConfig) {
|
|
78
|
+
await traceStep(
|
|
79
|
+
'Update vite configuration for sourcemap uploads',
|
|
80
|
+
async () => {
|
|
81
|
+
try {
|
|
82
|
+
await configureVitePlugin({
|
|
83
|
+
orgSlug: selectedProject.organization.slug,
|
|
84
|
+
projectSlug: selectedProject.slug,
|
|
85
|
+
url: sentryUrl,
|
|
86
|
+
selfHosted,
|
|
87
|
+
authToken,
|
|
88
|
+
});
|
|
89
|
+
} catch (e) {
|
|
90
|
+
clack.log
|
|
91
|
+
.warn(`Could not update vite configuration to generate and upload sourcemaps.
|
|
92
|
+
Please update your vite configuration manually using instructions from https://docs.sentry.io/platforms/javascript/guides/remix/sourcemaps/`);
|
|
93
|
+
debug(e);
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
);
|
|
97
|
+
} else {
|
|
98
|
+
await traceStep('Update build script for sourcemap uploads', async () => {
|
|
99
|
+
try {
|
|
100
|
+
await updateBuildScript({
|
|
101
|
+
org: selectedProject.organization.slug,
|
|
102
|
+
project: selectedProject.slug,
|
|
103
|
+
url: sentryUrl === DEFAULT_URL ? undefined : sentryUrl,
|
|
104
|
+
isHydrogen: isHydrogenApp(packageJson),
|
|
105
|
+
});
|
|
106
|
+
} catch (e) {
|
|
107
|
+
clack.log
|
|
108
|
+
.warn(`Could not update build script to generate and upload sourcemaps.
|
|
84
109
|
Please update your build script manually using instructions from https://docs.sentry.io/platforms/javascript/guides/remix/sourcemaps/`);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
110
|
+
debug(e);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
88
114
|
|
|
89
115
|
await traceStep('Instrument root route', async () => {
|
|
90
116
|
try {
|
|
@@ -96,6 +122,16 @@ async function runRemixWizardWithTelemetry(
|
|
|
96
122
|
}
|
|
97
123
|
});
|
|
98
124
|
|
|
125
|
+
traceStep('Reveal missing entry files', () => {
|
|
126
|
+
try {
|
|
127
|
+
runRemixReveal(isTS);
|
|
128
|
+
} catch (e) {
|
|
129
|
+
clack.log.warn(`Could not run 'npx remix reveal'.
|
|
130
|
+
Please create your entry files manually`);
|
|
131
|
+
debug(e);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
99
135
|
await traceStep('Initialize Sentry on client entry', async () => {
|
|
100
136
|
try {
|
|
101
137
|
await initializeSentryOnEntryClient(dsn, isTS);
|
package/src/remix/sdk-setup.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { ProxifiedModule } from 'magicast';
|
|
|
8
8
|
import * as fs from 'fs';
|
|
9
9
|
import * as path from 'path';
|
|
10
10
|
import * as url from 'url';
|
|
11
|
+
import * as childProcess from 'child_process';
|
|
11
12
|
|
|
12
13
|
// @ts-expect-error - clack is ESM and TS complains about that. It works though
|
|
13
14
|
import clack from '@clack/prompts';
|
|
@@ -36,6 +37,32 @@ export type PartialRemixConfig = {
|
|
|
36
37
|
};
|
|
37
38
|
|
|
38
39
|
const REMIX_CONFIG_FILE = 'remix.config.js';
|
|
40
|
+
const REMIX_REVEAL_COMMAND = 'npx remix reveal';
|
|
41
|
+
|
|
42
|
+
export function runRemixReveal(isTS: boolean): void {
|
|
43
|
+
// Check if entry files already exist
|
|
44
|
+
const clientEntryFilename = `entry.client.${isTS ? 'tsx' : 'jsx'}`;
|
|
45
|
+
const serverEntryFilename = `entry.server.${isTS ? 'tsx' : 'jsx'}`;
|
|
46
|
+
|
|
47
|
+
const clientEntryPath = path.join(process.cwd(), 'app', clientEntryFilename);
|
|
48
|
+
const serverEntryPath = path.join(process.cwd(), 'app', serverEntryFilename);
|
|
49
|
+
|
|
50
|
+
if (fs.existsSync(clientEntryPath) && fs.existsSync(serverEntryPath)) {
|
|
51
|
+
clack.log.info(
|
|
52
|
+
`Found entry files ${chalk.cyan(clientEntryFilename)} and ${chalk.cyan(
|
|
53
|
+
serverEntryFilename,
|
|
54
|
+
)}.`,
|
|
55
|
+
);
|
|
56
|
+
} else {
|
|
57
|
+
clack.log.info(
|
|
58
|
+
`Couldn't find entry files in your project. Trying to run ${chalk.cyan(
|
|
59
|
+
REMIX_REVEAL_COMMAND,
|
|
60
|
+
)}...`,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
clack.log.info(childProcess.execSync(REMIX_REVEAL_COMMAND).toString());
|
|
64
|
+
}
|
|
65
|
+
}
|
|
39
66
|
|
|
40
67
|
function insertClientInitCall(
|
|
41
68
|
dsn: string,
|
|
@@ -192,6 +219,10 @@ export async function updateBuildScript(args: {
|
|
|
192
219
|
buildCommand,
|
|
193
220
|
instrumentedBuildCommand,
|
|
194
221
|
);
|
|
222
|
+
} else {
|
|
223
|
+
throw new Error(
|
|
224
|
+
"`build` script doesn't contain a known build command. Please update it manually.",
|
|
225
|
+
);
|
|
195
226
|
}
|
|
196
227
|
|
|
197
228
|
await fs.promises.writeFile(
|
package/src/utils/ast-utils.ts
CHANGED
|
@@ -216,3 +216,55 @@ export function printJsonC(ast: t.Program): string {
|
|
|
216
216
|
const js = recast.print(ast).code;
|
|
217
217
|
return js.substring(1, js.length - 1);
|
|
218
218
|
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Walks the program body and returns index of the last variable assignment initialized by require statement.
|
|
222
|
+
* Only counts top level require statements.
|
|
223
|
+
*
|
|
224
|
+
* @returns index of the last `const foo = require('bar');` statement
|
|
225
|
+
*/
|
|
226
|
+
export function getLastRequireIndex(program: t.Program): number {
|
|
227
|
+
let lastRequireIdex = 0;
|
|
228
|
+
program.body.forEach((s, i) => {
|
|
229
|
+
if (
|
|
230
|
+
s.type === 'VariableDeclaration' &&
|
|
231
|
+
s.declarations[0].type === 'VariableDeclarator' &&
|
|
232
|
+
s.declarations[0].init !== null &&
|
|
233
|
+
typeof s.declarations[0].init !== 'undefined' &&
|
|
234
|
+
s.declarations[0].init.type === 'CallExpression' &&
|
|
235
|
+
s.declarations[0].init.callee.type === 'Identifier' &&
|
|
236
|
+
s.declarations[0].init.callee.name === 'require'
|
|
237
|
+
) {
|
|
238
|
+
lastRequireIdex = i;
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
return lastRequireIdex;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Walks the statements and removes require statements which first argument includes the predicate.
|
|
246
|
+
* Only removes top level require statements like `const foo = require('bar');`
|
|
247
|
+
*
|
|
248
|
+
* @returns True if any require statement was removed.
|
|
249
|
+
*/
|
|
250
|
+
export function removeRequire(program: t.Program, predicate: string): boolean {
|
|
251
|
+
let removedAtLeastOne = false;
|
|
252
|
+
program.body = program.body.filter((s) => {
|
|
253
|
+
if (
|
|
254
|
+
s.type === 'VariableDeclaration' &&
|
|
255
|
+
s.declarations[0].type === 'VariableDeclarator' &&
|
|
256
|
+
s.declarations[0].init !== null &&
|
|
257
|
+
typeof s.declarations[0].init !== 'undefined' &&
|
|
258
|
+
s.declarations[0].init.type === 'CallExpression' &&
|
|
259
|
+
s.declarations[0].init.callee.type === 'Identifier' &&
|
|
260
|
+
s.declarations[0].init.callee.name === 'require' &&
|
|
261
|
+
s.declarations[0].init.arguments[0].type === 'StringLiteral' &&
|
|
262
|
+
s.declarations[0].init.arguments[0].value.includes(predicate)
|
|
263
|
+
) {
|
|
264
|
+
removedAtLeastOne = true;
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
return true;
|
|
268
|
+
});
|
|
269
|
+
return removedAtLeastOne;
|
|
270
|
+
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
// @ts-ignore - magicast is ESM and TS complains about that. It works though
|
|
2
|
+
import { generateCode, type ProxifiedModule, parseModule } from 'magicast';
|
|
3
|
+
|
|
4
|
+
import * as recast from 'recast';
|
|
5
|
+
import x = recast.types;
|
|
6
|
+
import t = x.namedTypes;
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
addSentrySerializerRequireToMetroConfig,
|
|
10
|
+
addSentrySerializerToMetroConfig,
|
|
11
|
+
getMetroConfigObject,
|
|
12
|
+
removeSentryRequire,
|
|
13
|
+
removeSentrySerializerFromMetroConfig,
|
|
14
|
+
} from '../../src/react-native/metro';
|
|
15
|
+
|
|
16
|
+
describe('patch metro config - sentry serializer', () => {
|
|
17
|
+
describe('addSentrySerializerToMetroConfig', () => {
|
|
18
|
+
it('add to empty config', () => {
|
|
19
|
+
const mod = parseModule(`module.exports = {
|
|
20
|
+
other: 'config'
|
|
21
|
+
}`);
|
|
22
|
+
const configObject = getModuleExportsObject(mod);
|
|
23
|
+
const result = addSentrySerializerToMetroConfig(configObject);
|
|
24
|
+
expect(result).toBe(true);
|
|
25
|
+
expect(generateCode(mod.$ast).code).toBe(`module.exports = {
|
|
26
|
+
other: 'config',
|
|
27
|
+
|
|
28
|
+
serializer: {
|
|
29
|
+
customSerializer: createSentryMetroSerializer()
|
|
30
|
+
}
|
|
31
|
+
}`);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('add to existing serializer config', () => {
|
|
35
|
+
const mod = parseModule(`module.exports = {
|
|
36
|
+
other: 'config',
|
|
37
|
+
serializer: {
|
|
38
|
+
other: 'config'
|
|
39
|
+
}
|
|
40
|
+
}`);
|
|
41
|
+
const configObject = getModuleExportsObject(mod);
|
|
42
|
+
const result = addSentrySerializerToMetroConfig(configObject);
|
|
43
|
+
expect(result).toBe(true);
|
|
44
|
+
expect(generateCode(mod.$ast).code).toBe(`module.exports = {
|
|
45
|
+
other: 'config',
|
|
46
|
+
serializer: {
|
|
47
|
+
other: 'config',
|
|
48
|
+
customSerializer: createSentryMetroSerializer()
|
|
49
|
+
}
|
|
50
|
+
}`);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('not add to existing customSerializer config', () => {
|
|
54
|
+
const mod = parseModule(`module.exports = {
|
|
55
|
+
other: 'config',
|
|
56
|
+
serializer: {
|
|
57
|
+
other: 'config',
|
|
58
|
+
customSerializer: 'existing-serializer'
|
|
59
|
+
}
|
|
60
|
+
}`);
|
|
61
|
+
const configObject = getModuleExportsObject(mod);
|
|
62
|
+
const result = addSentrySerializerToMetroConfig(configObject);
|
|
63
|
+
expect(result).toBe(false);
|
|
64
|
+
expect(generateCode(mod.$ast).code).toBe(`module.exports = {
|
|
65
|
+
other: 'config',
|
|
66
|
+
serializer: {
|
|
67
|
+
other: 'config',
|
|
68
|
+
customSerializer: 'existing-serializer'
|
|
69
|
+
}
|
|
70
|
+
}`);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('addSentrySerializerImportToMetroConfig', () => {
|
|
75
|
+
it('add import', () => {
|
|
76
|
+
const mod =
|
|
77
|
+
parseModule(`const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
|
|
78
|
+
|
|
79
|
+
module.exports = {
|
|
80
|
+
other: 'config'
|
|
81
|
+
}`);
|
|
82
|
+
const result = addSentrySerializerRequireToMetroConfig(
|
|
83
|
+
mod.$ast as t.Program,
|
|
84
|
+
);
|
|
85
|
+
expect(result).toBe(true);
|
|
86
|
+
expect(generateCode(mod.$ast).code)
|
|
87
|
+
.toBe(`const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
|
|
88
|
+
|
|
89
|
+
const {
|
|
90
|
+
createSentryMetroSerializer
|
|
91
|
+
} = require("@sentry/react-native/dist/js/tools/sentryMetroSerializer");
|
|
92
|
+
|
|
93
|
+
module.exports = {
|
|
94
|
+
other: 'config'
|
|
95
|
+
}`);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe('getMetroConfigObject', () => {
|
|
100
|
+
it('get config object from variable called config', () => {
|
|
101
|
+
const mod = parseModule(`var config = { some: 'config' };`);
|
|
102
|
+
const configObject = getMetroConfigObject(mod.$ast as t.Program);
|
|
103
|
+
expect(
|
|
104
|
+
((configObject?.properties[0] as t.ObjectProperty).key as t.Identifier)
|
|
105
|
+
.name,
|
|
106
|
+
).toBe('some');
|
|
107
|
+
expect(
|
|
108
|
+
(
|
|
109
|
+
(configObject?.properties[0] as t.ObjectProperty)
|
|
110
|
+
.value as t.StringLiteral
|
|
111
|
+
).value,
|
|
112
|
+
).toBe('config');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('get config object from const called config', () => {
|
|
116
|
+
const mod = parseModule(`const config = { some: 'config' };`);
|
|
117
|
+
const configObject = getMetroConfigObject(mod.$ast as t.Program);
|
|
118
|
+
expect(
|
|
119
|
+
((configObject?.properties[0] as t.ObjectProperty).key as t.Identifier)
|
|
120
|
+
.name,
|
|
121
|
+
).toBe('some');
|
|
122
|
+
expect(
|
|
123
|
+
(
|
|
124
|
+
(configObject?.properties[0] as t.ObjectProperty)
|
|
125
|
+
.value as t.StringLiteral
|
|
126
|
+
).value,
|
|
127
|
+
).toBe('config');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('get config oject from let called config', () => {
|
|
131
|
+
const mod = parseModule(`let config = { some: 'config' };`);
|
|
132
|
+
const configObject = getMetroConfigObject(mod.$ast as t.Program);
|
|
133
|
+
expect(
|
|
134
|
+
((configObject?.properties[0] as t.ObjectProperty).key as t.Identifier)
|
|
135
|
+
.name,
|
|
136
|
+
).toBe('some');
|
|
137
|
+
expect(
|
|
138
|
+
(
|
|
139
|
+
(configObject?.properties[0] as t.ObjectProperty)
|
|
140
|
+
.value as t.StringLiteral
|
|
141
|
+
).value,
|
|
142
|
+
).toBe('config');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('get config object from module.exports', () => {
|
|
146
|
+
const mod = parseModule(`module.exports = { some: 'config' };`);
|
|
147
|
+
const configObject = getMetroConfigObject(mod.$ast as t.Program);
|
|
148
|
+
expect(
|
|
149
|
+
((configObject?.properties[0] as t.ObjectProperty).key as t.Identifier)
|
|
150
|
+
.name,
|
|
151
|
+
).toBe('some');
|
|
152
|
+
expect(
|
|
153
|
+
(
|
|
154
|
+
(configObject?.properties[0] as t.ObjectProperty)
|
|
155
|
+
.value as t.StringLiteral
|
|
156
|
+
).value,
|
|
157
|
+
).toBe('config');
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe('remove @sentry require', () => {
|
|
162
|
+
it('nothing to remove', () => {
|
|
163
|
+
const mod = parseModule(`let config = { some: 'config' };`);
|
|
164
|
+
const result = removeSentryRequire(mod.$ast as t.Program);
|
|
165
|
+
expect(result).toBe(false);
|
|
166
|
+
expect(generateCode(mod.$ast).code).toBe(
|
|
167
|
+
`let config = { some: 'config' };`,
|
|
168
|
+
);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('remove metro serializer import', () => {
|
|
172
|
+
const mod = parseModule(`const {
|
|
173
|
+
createSentryMetroSerializer,
|
|
174
|
+
} = require('@sentry/react-native/dist/js/tools/sentryMetroSerializer');
|
|
175
|
+
let config = { some: 'config' };`);
|
|
176
|
+
const result = removeSentryRequire(mod.$ast as t.Program);
|
|
177
|
+
expect(result).toBe(true);
|
|
178
|
+
expect(generateCode(mod.$ast).code).toBe(
|
|
179
|
+
`let config = { some: 'config' };`,
|
|
180
|
+
);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('remove all sentry imports', () => {
|
|
184
|
+
const mod = parseModule(`const {
|
|
185
|
+
createSentryMetroSerializer,
|
|
186
|
+
} = require('@sentry/react-native/dist/js/tools/sentryMetroSerializer');
|
|
187
|
+
var Sentry = require('@sentry/react-native');
|
|
188
|
+
let SentryIntegrations = require('@sentry/integrations');
|
|
189
|
+
|
|
190
|
+
let config = { some: 'config' };`);
|
|
191
|
+
const result = removeSentryRequire(mod.$ast as t.Program);
|
|
192
|
+
expect(result).toBe(true);
|
|
193
|
+
expect(generateCode(mod.$ast).code).toBe(
|
|
194
|
+
`let config = { some: 'config' };`,
|
|
195
|
+
);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe('remove sentryMetroSerializer', () => {
|
|
200
|
+
it('no custom serializer to remove', () => {
|
|
201
|
+
const mod = parseModule(`let config = { some: 'config' };`);
|
|
202
|
+
const result = removeSentrySerializerFromMetroConfig(
|
|
203
|
+
mod.$ast as t.Program,
|
|
204
|
+
);
|
|
205
|
+
expect(result).toBe(false);
|
|
206
|
+
expect(generateCode(mod.$ast).code).toBe(
|
|
207
|
+
`let config = { some: 'config' };`,
|
|
208
|
+
);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('no Sentry custom serializer to remove', () => {
|
|
212
|
+
const mod = parseModule(`let config = {
|
|
213
|
+
serializer: {
|
|
214
|
+
customSerializer: 'existing-serializer',
|
|
215
|
+
other: 'config',
|
|
216
|
+
},
|
|
217
|
+
other: 'config',
|
|
218
|
+
};`);
|
|
219
|
+
const result = removeSentrySerializerFromMetroConfig(
|
|
220
|
+
mod.$ast as t.Program,
|
|
221
|
+
);
|
|
222
|
+
expect(result).toBe(false);
|
|
223
|
+
expect(generateCode(mod.$ast).code).toBe(`let config = {
|
|
224
|
+
serializer: {
|
|
225
|
+
customSerializer: 'existing-serializer',
|
|
226
|
+
other: 'config',
|
|
227
|
+
},
|
|
228
|
+
other: 'config',
|
|
229
|
+
};`);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('Sentry serializer to remove', () => {
|
|
233
|
+
const mod = parseModule(`let config = {
|
|
234
|
+
serializer: {
|
|
235
|
+
customSerializer: createSentryMetroSerializer(),
|
|
236
|
+
other: 'config',
|
|
237
|
+
},
|
|
238
|
+
other: 'config',
|
|
239
|
+
};`);
|
|
240
|
+
const result = removeSentrySerializerFromMetroConfig(
|
|
241
|
+
mod.$ast as t.Program,
|
|
242
|
+
);
|
|
243
|
+
expect(result).toBe(true);
|
|
244
|
+
expect(generateCode(mod.$ast).code).toBe(`let config = {
|
|
245
|
+
serializer: {
|
|
246
|
+
other: 'config'
|
|
247
|
+
},
|
|
248
|
+
other: 'config',
|
|
249
|
+
};`);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('Sentry serializer to remove with wrapped serializer', () => {
|
|
253
|
+
const mod = parseModule(`let config = {
|
|
254
|
+
serializer: {
|
|
255
|
+
customSerializer: createSentryMetroSerializer(wrappedSerializer()),
|
|
256
|
+
other: 'config',
|
|
257
|
+
},
|
|
258
|
+
other: 'config',
|
|
259
|
+
};`);
|
|
260
|
+
const result = removeSentrySerializerFromMetroConfig(
|
|
261
|
+
mod.$ast as t.Program,
|
|
262
|
+
);
|
|
263
|
+
expect(result).toBe(true);
|
|
264
|
+
expect(generateCode(mod.$ast).code).toBe(`let config = {
|
|
265
|
+
serializer: {
|
|
266
|
+
customSerializer: wrappedSerializer(),
|
|
267
|
+
other: 'config',
|
|
268
|
+
},
|
|
269
|
+
other: 'config',
|
|
270
|
+
};`);
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
function getModuleExportsObject(
|
|
276
|
+
mod: ProxifiedModule,
|
|
277
|
+
index = 0,
|
|
278
|
+
): t.ObjectExpression {
|
|
279
|
+
return (
|
|
280
|
+
((mod.$ast as t.Program).body[index] as t.ExpressionStatement)
|
|
281
|
+
.expression as t.AssignmentExpression
|
|
282
|
+
).right as t.ObjectExpression;
|
|
283
|
+
}
|