@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
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
// @ts-ignore - clack is ESM and TS complains about that. It works though
|
|
2
|
+
import * as clack from '@clack/prompts';
|
|
3
|
+
// @ts-ignore - magicast is ESM and TS complains about that. It works though
|
|
4
|
+
import { ProxifiedModule, parseModule, writeFile } from 'magicast';
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as Sentry from '@sentry/node';
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
getLastRequireIndex,
|
|
10
|
+
hasSentryContent,
|
|
11
|
+
removeRequire,
|
|
12
|
+
} from '../utils/ast-utils';
|
|
13
|
+
import {
|
|
14
|
+
abortIfCancelled,
|
|
15
|
+
makeCodeSnippet,
|
|
16
|
+
showCopyPasteInstructions,
|
|
17
|
+
} from '../utils/clack-utils';
|
|
18
|
+
|
|
19
|
+
import * as recast from 'recast';
|
|
20
|
+
import x = recast.types;
|
|
21
|
+
import t = x.namedTypes;
|
|
22
|
+
import chalk from 'chalk';
|
|
23
|
+
|
|
24
|
+
const b = recast.types.builders;
|
|
25
|
+
|
|
26
|
+
const metroConfigPath = 'metro.config.js';
|
|
27
|
+
|
|
28
|
+
export async function patchMetroConfig() {
|
|
29
|
+
const mod = await parseMetroConfig();
|
|
30
|
+
|
|
31
|
+
const showInstructions = () =>
|
|
32
|
+
showCopyPasteInstructions(metroConfigPath, getMetroConfigSnippet(true));
|
|
33
|
+
|
|
34
|
+
if (hasSentryContent(mod.$ast as t.Program)) {
|
|
35
|
+
const shouldContinue = await confirmPathMetroConfig();
|
|
36
|
+
if (!shouldContinue) {
|
|
37
|
+
return await showInstructions();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const configObj = getMetroConfigObject(mod.$ast as t.Program);
|
|
42
|
+
if (!configObj) {
|
|
43
|
+
clack.log.warn(
|
|
44
|
+
'Could not find Metro config object, please follow the manual steps.',
|
|
45
|
+
);
|
|
46
|
+
return showInstructions();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const addedSentrySerializer = addSentrySerializerToMetroConfig(configObj);
|
|
50
|
+
if (!addedSentrySerializer) {
|
|
51
|
+
clack.log.warn(
|
|
52
|
+
'Could not add Sentry serializer to Metro config, please follow the manual steps.',
|
|
53
|
+
);
|
|
54
|
+
return await showInstructions();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const addedSentrySerializerImport = addSentrySerializerRequireToMetroConfig(
|
|
58
|
+
mod.$ast as t.Program,
|
|
59
|
+
);
|
|
60
|
+
if (!addedSentrySerializerImport) {
|
|
61
|
+
clack.log.warn(
|
|
62
|
+
'Could not add Sentry serializer import to Metro config, please follow the manual steps.',
|
|
63
|
+
);
|
|
64
|
+
return await showInstructions();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
clack.log.success(
|
|
68
|
+
`Added Sentry Metro plugin to ${chalk.cyan(metroConfigPath)}.`,
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const saved = await writeMetroConfig(mod);
|
|
72
|
+
if (saved) {
|
|
73
|
+
clack.log.success(
|
|
74
|
+
chalk.green(`${chalk.cyan(metroConfigPath)} changes saved.`),
|
|
75
|
+
);
|
|
76
|
+
} else {
|
|
77
|
+
clack.log.warn(
|
|
78
|
+
`Could not save changes to ${chalk.cyan(
|
|
79
|
+
metroConfigPath,
|
|
80
|
+
)}, please follow the manual steps.`,
|
|
81
|
+
);
|
|
82
|
+
return await showInstructions();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function unPatchMetroConfig() {
|
|
87
|
+
const mod = await parseMetroConfig();
|
|
88
|
+
|
|
89
|
+
const removedAtLeastOneRequire = removeSentryRequire(mod.$ast as t.Program);
|
|
90
|
+
const removedSerializerConfig = removeSentrySerializerFromMetroConfig(
|
|
91
|
+
mod.$ast as t.Program,
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
if (removedAtLeastOneRequire || removedSerializerConfig) {
|
|
95
|
+
const saved = await writeMetroConfig(mod);
|
|
96
|
+
if (saved) {
|
|
97
|
+
clack.log.success(
|
|
98
|
+
`Removed Sentry Metro plugin from ${chalk.cyan(metroConfigPath)}.`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
clack.log.warn(
|
|
103
|
+
`No Sentry Metro plugin found in ${chalk.cyan(metroConfigPath)}.`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function removeSentrySerializerFromMetroConfig(
|
|
109
|
+
program: t.Program,
|
|
110
|
+
): boolean {
|
|
111
|
+
const configObject = getMetroConfigObject(program);
|
|
112
|
+
if (!configObject) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const serializerProp = getSerializerProp(configObject);
|
|
117
|
+
if ('invalid' === serializerProp || 'undefined' === serializerProp) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const customSerializerProp = getCustomSerializerProp(serializerProp);
|
|
122
|
+
if (
|
|
123
|
+
'invalid' === customSerializerProp ||
|
|
124
|
+
'undefined' === customSerializerProp
|
|
125
|
+
) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (
|
|
130
|
+
serializerProp.value.type === 'ObjectExpression' &&
|
|
131
|
+
customSerializerProp.value.type === 'CallExpression' &&
|
|
132
|
+
customSerializerProp.value.callee.type === 'Identifier' &&
|
|
133
|
+
customSerializerProp.value.callee.name === 'createSentryMetroSerializer'
|
|
134
|
+
) {
|
|
135
|
+
if (customSerializerProp.value.arguments.length === 0) {
|
|
136
|
+
// FROM serializer: { customSerializer: createSentryMetroSerializer() }
|
|
137
|
+
// TO serializer: {}
|
|
138
|
+
let removed = false;
|
|
139
|
+
serializerProp.value.properties = serializerProp.value.properties.filter(
|
|
140
|
+
(p) => {
|
|
141
|
+
if (
|
|
142
|
+
p.type === 'ObjectProperty' &&
|
|
143
|
+
p.key.type === 'Identifier' &&
|
|
144
|
+
p.key.name === 'customSerializer'
|
|
145
|
+
) {
|
|
146
|
+
removed = true;
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
return true;
|
|
150
|
+
},
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
if (removed) {
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
} else {
|
|
157
|
+
if (customSerializerProp.value.arguments[0].type !== 'SpreadElement') {
|
|
158
|
+
// FROM serializer: { customSerializer: createSentryMetroSerializer(wrapperSerializer) }
|
|
159
|
+
// TO serializer: { customSerializer: wrapperSerializer }
|
|
160
|
+
customSerializerProp.value = customSerializerProp.value.arguments[0];
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function removeSentryRequire(program: t.Program): boolean {
|
|
170
|
+
return removeRequire(program, '@sentry');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function parseMetroConfig(): Promise<ProxifiedModule> {
|
|
174
|
+
const metroConfigContent = (
|
|
175
|
+
await fs.promises.readFile(metroConfigPath)
|
|
176
|
+
).toString();
|
|
177
|
+
|
|
178
|
+
return parseModule(metroConfigContent);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function writeMetroConfig(mod: ProxifiedModule): Promise<boolean> {
|
|
182
|
+
try {
|
|
183
|
+
await writeFile(mod.$ast, metroConfigPath);
|
|
184
|
+
} catch (e) {
|
|
185
|
+
clack.log.error(
|
|
186
|
+
`Failed to write to ${chalk.cyan(metroConfigPath)}: ${JSON.stringify(e)}`,
|
|
187
|
+
);
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function addSentrySerializerToMetroConfig(
|
|
194
|
+
configObj: t.ObjectExpression,
|
|
195
|
+
): boolean {
|
|
196
|
+
const serializerProp = getSerializerProp(configObj);
|
|
197
|
+
if ('invalid' === serializerProp) {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// case 1: serializer property doesn't exist yet, so we can just add it
|
|
202
|
+
if ('undefined' === serializerProp) {
|
|
203
|
+
configObj.properties.push(
|
|
204
|
+
b.objectProperty(
|
|
205
|
+
b.identifier('serializer'),
|
|
206
|
+
b.objectExpression([
|
|
207
|
+
b.objectProperty(
|
|
208
|
+
b.identifier('customSerializer'),
|
|
209
|
+
b.callExpression(b.identifier('createSentryMetroSerializer'), []),
|
|
210
|
+
),
|
|
211
|
+
]),
|
|
212
|
+
),
|
|
213
|
+
);
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const customSerializerProp = getCustomSerializerProp(serializerProp);
|
|
218
|
+
// case 2: serializer.customSerializer property doesn't exist yet, so we just add it
|
|
219
|
+
if (
|
|
220
|
+
'undefined' === customSerializerProp &&
|
|
221
|
+
serializerProp.value.type === 'ObjectExpression'
|
|
222
|
+
) {
|
|
223
|
+
serializerProp.value.properties.push(
|
|
224
|
+
b.objectProperty(
|
|
225
|
+
b.identifier('customSerializer'),
|
|
226
|
+
b.callExpression(b.identifier('createSentryMetroSerializer'), []),
|
|
227
|
+
),
|
|
228
|
+
);
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function getCustomSerializerProp(
|
|
236
|
+
prop: t.ObjectProperty,
|
|
237
|
+
): t.ObjectProperty | 'undefined' | 'invalid' {
|
|
238
|
+
const customSerializerProp =
|
|
239
|
+
prop.value.type === 'ObjectExpression' &&
|
|
240
|
+
prop.value.properties.find(
|
|
241
|
+
(p: t.ObjectProperty) =>
|
|
242
|
+
p.key.type === 'Identifier' && p.key.name === 'customSerializer',
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
if (!customSerializerProp) {
|
|
246
|
+
return 'undefined';
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (customSerializerProp.type === 'ObjectProperty') {
|
|
250
|
+
return customSerializerProp;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return 'invalid';
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function getSerializerProp(
|
|
257
|
+
obj: t.ObjectExpression,
|
|
258
|
+
): t.ObjectProperty | 'undefined' | 'invalid' {
|
|
259
|
+
const serializerProp = obj.properties.find(
|
|
260
|
+
(p: t.ObjectProperty) =>
|
|
261
|
+
p.key.type === 'Identifier' && p.key.name === 'serializer',
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
if (!serializerProp) {
|
|
265
|
+
return 'undefined';
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (serializerProp.type === 'ObjectProperty') {
|
|
269
|
+
return serializerProp;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return 'invalid';
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function addSentrySerializerRequireToMetroConfig(
|
|
276
|
+
program: t.Program,
|
|
277
|
+
): boolean {
|
|
278
|
+
const lastRequireIndex = getLastRequireIndex(program);
|
|
279
|
+
const sentrySerializerRequire = createSentrySerializerRequire();
|
|
280
|
+
const sentryImportIndex = lastRequireIndex + 1;
|
|
281
|
+
if (sentryImportIndex < program.body.length) {
|
|
282
|
+
// insert after last require
|
|
283
|
+
program.body.splice(lastRequireIndex + 1, 0, sentrySerializerRequire);
|
|
284
|
+
} else {
|
|
285
|
+
// insert at the end
|
|
286
|
+
program.body.push(sentrySerializerRequire);
|
|
287
|
+
}
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Creates const {createSentryMetroSerializer} = require('@sentry/react-native/dist/js/tools/sentryMetroSerializer');
|
|
293
|
+
*/
|
|
294
|
+
function createSentrySerializerRequire() {
|
|
295
|
+
return b.variableDeclaration('const', [
|
|
296
|
+
b.variableDeclarator(
|
|
297
|
+
b.objectPattern([
|
|
298
|
+
b.objectProperty.from({
|
|
299
|
+
key: b.identifier('createSentryMetroSerializer'),
|
|
300
|
+
value: b.identifier('createSentryMetroSerializer'),
|
|
301
|
+
shorthand: true,
|
|
302
|
+
}),
|
|
303
|
+
]),
|
|
304
|
+
b.callExpression(b.identifier('require'), [
|
|
305
|
+
b.literal('@sentry/react-native/dist/js/tools/sentryMetroSerializer'),
|
|
306
|
+
]),
|
|
307
|
+
),
|
|
308
|
+
]);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async function confirmPathMetroConfig() {
|
|
312
|
+
const shouldContinue = await abortIfCancelled(
|
|
313
|
+
clack.select({
|
|
314
|
+
message: `Metro Config already contains Sentry-related code. Should the wizard modify it anyway?`,
|
|
315
|
+
options: [
|
|
316
|
+
{
|
|
317
|
+
label: 'Yes, add the Sentry Metro plugin',
|
|
318
|
+
value: true,
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
label: 'No, show me instructions to manually add the plugin',
|
|
322
|
+
value: false,
|
|
323
|
+
},
|
|
324
|
+
],
|
|
325
|
+
initialValue: true,
|
|
326
|
+
}),
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
if (!shouldContinue) {
|
|
330
|
+
Sentry.setTag('ast-mod-fail-reason', 'has-sentry-content');
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return shouldContinue;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Returns value from `module.exports = value` or `const config = value`
|
|
338
|
+
*/
|
|
339
|
+
export function getMetroConfigObject(
|
|
340
|
+
program: t.Program,
|
|
341
|
+
): t.ObjectExpression | undefined {
|
|
342
|
+
// check config variable
|
|
343
|
+
const configVariable = program.body.find((s) => {
|
|
344
|
+
if (
|
|
345
|
+
s.type === 'VariableDeclaration' &&
|
|
346
|
+
s.declarations.length === 1 &&
|
|
347
|
+
s.declarations[0].type === 'VariableDeclarator' &&
|
|
348
|
+
s.declarations[0].id.type === 'Identifier' &&
|
|
349
|
+
s.declarations[0].id.name === 'config'
|
|
350
|
+
) {
|
|
351
|
+
return true;
|
|
352
|
+
}
|
|
353
|
+
return false;
|
|
354
|
+
}) as t.VariableDeclaration | undefined;
|
|
355
|
+
|
|
356
|
+
if (
|
|
357
|
+
configVariable?.declarations[0].type === 'VariableDeclarator' &&
|
|
358
|
+
configVariable?.declarations[0].init?.type === 'ObjectExpression'
|
|
359
|
+
) {
|
|
360
|
+
Sentry.setTag('metro-config', 'config-variable');
|
|
361
|
+
return configVariable.declarations[0].init;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// check module.exports
|
|
365
|
+
const moduleExports = program.body.find((s) => {
|
|
366
|
+
if (
|
|
367
|
+
s.type === 'ExpressionStatement' &&
|
|
368
|
+
s.expression.type === 'AssignmentExpression' &&
|
|
369
|
+
s.expression.left.type === 'MemberExpression' &&
|
|
370
|
+
s.expression.left.object.type === 'Identifier' &&
|
|
371
|
+
s.expression.left.object.name === 'module' &&
|
|
372
|
+
s.expression.left.property.type === 'Identifier' &&
|
|
373
|
+
s.expression.left.property.name === 'exports'
|
|
374
|
+
) {
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
return false;
|
|
378
|
+
}) as t.ExpressionStatement | undefined;
|
|
379
|
+
|
|
380
|
+
if (
|
|
381
|
+
(moduleExports?.expression as t.AssignmentExpression).right.type ===
|
|
382
|
+
'ObjectExpression'
|
|
383
|
+
) {
|
|
384
|
+
Sentry.setTag('metro-config', 'module-exports');
|
|
385
|
+
return (moduleExports?.expression as t.AssignmentExpression)
|
|
386
|
+
.right as t.ObjectExpression;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
Sentry.setTag('metro-config', 'not-found');
|
|
390
|
+
return undefined;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function getMetroConfigSnippet(colors: boolean) {
|
|
394
|
+
return makeCodeSnippet(colors, (unchanged, plus, _) =>
|
|
395
|
+
unchanged(`const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');";
|
|
396
|
+
${plus(
|
|
397
|
+
"const {createSentryMetroSerializer} = require('@sentry/react-native/dist/js/tools/sentryMetroSerializer');",
|
|
398
|
+
)}
|
|
399
|
+
|
|
400
|
+
const config = {
|
|
401
|
+
${plus(`serializer: {
|
|
402
|
+
customSerializer: createSentryMetroSerializer(),
|
|
403
|
+
},`)}
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
module.exports = mergeConfig(getDefaultConfig(__dirname), config);
|
|
407
|
+
`),
|
|
408
|
+
);
|
|
409
|
+
}
|
|
@@ -7,6 +7,7 @@ import * as path from 'path';
|
|
|
7
7
|
import * as process from 'process';
|
|
8
8
|
import {
|
|
9
9
|
CliSetupConfigContent,
|
|
10
|
+
abortIfCancelled,
|
|
10
11
|
addSentryCliConfig,
|
|
11
12
|
confirmContinueIfNoOrDirtyGitRepo,
|
|
12
13
|
confirmContinueIfPackageVersionNotSupported,
|
|
@@ -26,8 +27,11 @@ import {
|
|
|
26
27
|
findBundlePhase,
|
|
27
28
|
patchBundlePhase,
|
|
28
29
|
findDebugFilesUploadPhase,
|
|
29
|
-
|
|
30
|
+
addDebugFilesUploadPhaseWithCli,
|
|
30
31
|
writeXcodeProject,
|
|
32
|
+
addSentryWithCliToBundleShellScript,
|
|
33
|
+
addSentryWithBundledScriptsToBundleShellScript,
|
|
34
|
+
addDebugFilesUploadPhaseWithBundledScripts,
|
|
31
35
|
} from './xcode';
|
|
32
36
|
import {
|
|
33
37
|
doesAppBuildGradleIncludeRNSentryGradlePlugin,
|
|
@@ -44,7 +48,9 @@ import {
|
|
|
44
48
|
} from './javascript';
|
|
45
49
|
import { traceStep, withTelemetry } from '../telemetry';
|
|
46
50
|
import * as Sentry from '@sentry/node';
|
|
51
|
+
import { fulfillsVersionRange } from '../utils/semver';
|
|
47
52
|
import { getIssueStreamUrl } from '../utils/url';
|
|
53
|
+
import { patchMetroConfig } from './metro';
|
|
48
54
|
|
|
49
55
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
50
56
|
const xcode = require('xcode');
|
|
@@ -56,6 +62,13 @@ export const RN_HUMAN_NAME = 'React Native';
|
|
|
56
62
|
|
|
57
63
|
export const SUPPORTED_RN_RANGE = '>=0.69.0';
|
|
58
64
|
|
|
65
|
+
// The following SDK version ship with bundled Xcode scripts
|
|
66
|
+
// which simplifies the Xcode Build Phases setup.
|
|
67
|
+
export const SDK_XCODE_SCRIPTS_SUPPORTED_SDK_RANGE = '>=5.11.0';
|
|
68
|
+
|
|
69
|
+
// The following SDK version ship with Sentry Metro plugin
|
|
70
|
+
export const SDK_SENTRY_METRO_PLUGIN_SUPPORTED_SDK_RANGE = '>=5.11.0';
|
|
71
|
+
|
|
59
72
|
export type RNCliSetupConfigContent = Pick<
|
|
60
73
|
Required<CliSetupConfigContent>,
|
|
61
74
|
'authToken' | 'org' | 'project' | 'url'
|
|
@@ -119,14 +132,24 @@ export async function runReactNativeWizardWithTelemetry(
|
|
|
119
132
|
packageName: RN_SDK_PACKAGE,
|
|
120
133
|
alreadyInstalled: hasPackageInstalled(RN_SDK_PACKAGE, packageJson),
|
|
121
134
|
});
|
|
135
|
+
const sdkVersion = getPackageVersion(
|
|
136
|
+
RN_SDK_PACKAGE,
|
|
137
|
+
await getPackageDotJson(),
|
|
138
|
+
);
|
|
122
139
|
|
|
123
140
|
await traceStep('patch-js', () =>
|
|
124
141
|
addSentryInit({ dsn: selectedProject.keys[0].dsn.public }),
|
|
125
142
|
);
|
|
126
143
|
|
|
144
|
+
await traceStep('patch-metro-config', () =>
|
|
145
|
+
addSentryToMetroConfig({ sdkVersion }),
|
|
146
|
+
);
|
|
147
|
+
|
|
127
148
|
if (fs.existsSync('ios')) {
|
|
128
149
|
Sentry.setTag('patch-ios', true);
|
|
129
|
-
await traceStep('patch-xcode-files', () =>
|
|
150
|
+
await traceStep('patch-xcode-files', () =>
|
|
151
|
+
patchXcodeFiles(cliConfig, { sdkVersion }),
|
|
152
|
+
);
|
|
130
153
|
}
|
|
131
154
|
|
|
132
155
|
if (fs.existsSync('android')) {
|
|
@@ -158,6 +181,25 @@ export async function runReactNativeWizardWithTelemetry(
|
|
|
158
181
|
}
|
|
159
182
|
}
|
|
160
183
|
|
|
184
|
+
async function addSentryToMetroConfig({
|
|
185
|
+
sdkVersion,
|
|
186
|
+
}: {
|
|
187
|
+
sdkVersion: string | undefined;
|
|
188
|
+
}) {
|
|
189
|
+
if (
|
|
190
|
+
!sdkVersion ||
|
|
191
|
+
!fulfillsVersionRange({
|
|
192
|
+
version: sdkVersion,
|
|
193
|
+
acceptableVersions: SDK_SENTRY_METRO_PLUGIN_SUPPORTED_SDK_RANGE,
|
|
194
|
+
canBeLatest: true,
|
|
195
|
+
})
|
|
196
|
+
) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
await patchMetroConfig();
|
|
201
|
+
}
|
|
202
|
+
|
|
161
203
|
async function addSentryInit({ dsn }: { dsn: string }) {
|
|
162
204
|
const prefixGlob = '{.,./src}';
|
|
163
205
|
const suffixGlob = '@(j|t|cj|mj)s?(x)';
|
|
@@ -240,7 +282,12 @@ ${chalk.cyan(issuesStreamUrl)}`);
|
|
|
240
282
|
return firstErrorConfirmed;
|
|
241
283
|
}
|
|
242
284
|
|
|
243
|
-
async function patchXcodeFiles(
|
|
285
|
+
async function patchXcodeFiles(
|
|
286
|
+
config: RNCliSetupConfigContent,
|
|
287
|
+
context: {
|
|
288
|
+
sdkVersion: string | undefined;
|
|
289
|
+
},
|
|
290
|
+
) {
|
|
244
291
|
await addSentryCliConfig(config, {
|
|
245
292
|
...propertiesCliSetupConfig,
|
|
246
293
|
name: 'source maps and iOS debug files',
|
|
@@ -248,9 +295,8 @@ async function patchXcodeFiles(config: RNCliSetupConfigContent) {
|
|
|
248
295
|
gitignore: false,
|
|
249
296
|
});
|
|
250
297
|
|
|
251
|
-
if (platform() === 'darwin') {
|
|
298
|
+
if (platform() === 'darwin' && (await confirmPodInstall())) {
|
|
252
299
|
await traceStep('pod-install', () => podInstall('ios'));
|
|
253
|
-
Sentry.setTag('pods-installed', true);
|
|
254
300
|
}
|
|
255
301
|
|
|
256
302
|
const xcodeProjectPath = traceStep('find-xcode-project', () =>
|
|
@@ -288,7 +334,21 @@ async function patchXcodeFiles(config: RNCliSetupConfigContent) {
|
|
|
288
334
|
'xcode-bundle-phase-status',
|
|
289
335
|
bundlePhase ? 'found' : 'not-found',
|
|
290
336
|
);
|
|
291
|
-
|
|
337
|
+
if (
|
|
338
|
+
context.sdkVersion &&
|
|
339
|
+
fulfillsVersionRange({
|
|
340
|
+
version: context.sdkVersion,
|
|
341
|
+
acceptableVersions: SDK_XCODE_SCRIPTS_SUPPORTED_SDK_RANGE,
|
|
342
|
+
canBeLatest: true,
|
|
343
|
+
})
|
|
344
|
+
) {
|
|
345
|
+
patchBundlePhase(
|
|
346
|
+
bundlePhase,
|
|
347
|
+
addSentryWithBundledScriptsToBundleShellScript,
|
|
348
|
+
);
|
|
349
|
+
} else {
|
|
350
|
+
patchBundlePhase(bundlePhase, addSentryWithCliToBundleShellScript);
|
|
351
|
+
}
|
|
292
352
|
Sentry.setTag('xcode-bundle-phase-status', 'patched');
|
|
293
353
|
});
|
|
294
354
|
|
|
@@ -299,7 +359,22 @@ async function patchXcodeFiles(config: RNCliSetupConfigContent) {
|
|
|
299
359
|
'xcode-debug-files-upload-phase-status',
|
|
300
360
|
debugFilesUploadPhaseExists ? 'already-exists' : undefined,
|
|
301
361
|
);
|
|
302
|
-
|
|
362
|
+
if (
|
|
363
|
+
context.sdkVersion &&
|
|
364
|
+
fulfillsVersionRange({
|
|
365
|
+
version: context.sdkVersion,
|
|
366
|
+
acceptableVersions: SDK_XCODE_SCRIPTS_SUPPORTED_SDK_RANGE,
|
|
367
|
+
canBeLatest: true,
|
|
368
|
+
})
|
|
369
|
+
) {
|
|
370
|
+
addDebugFilesUploadPhaseWithBundledScripts(xcodeProject, {
|
|
371
|
+
debugFilesUploadPhaseExists,
|
|
372
|
+
});
|
|
373
|
+
} else {
|
|
374
|
+
addDebugFilesUploadPhaseWithCli(xcodeProject, {
|
|
375
|
+
debugFilesUploadPhaseExists,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
303
378
|
Sentry.setTag('xcode-debug-files-upload-phase-status', 'added');
|
|
304
379
|
});
|
|
305
380
|
|
|
@@ -374,3 +449,24 @@ async function patchAndroidFiles(config: RNCliSetupConfigContent) {
|
|
|
374
449
|
chalk.green(`Android ${chalk.cyan('app/build.gradle')} saved.`),
|
|
375
450
|
);
|
|
376
451
|
}
|
|
452
|
+
|
|
453
|
+
async function confirmPodInstall(): Promise<boolean> {
|
|
454
|
+
return traceStep('confirm-pod-install', async () => {
|
|
455
|
+
const continueWithPodInstall = await abortIfCancelled(
|
|
456
|
+
clack.select({
|
|
457
|
+
message: 'Do you want to run `pod install` now?',
|
|
458
|
+
options: [
|
|
459
|
+
{
|
|
460
|
+
value: true,
|
|
461
|
+
label: 'Yes',
|
|
462
|
+
hint: 'Recommended for smaller projects, this might take several minutes',
|
|
463
|
+
},
|
|
464
|
+
{ value: false, label: `No, I'll do it later` },
|
|
465
|
+
],
|
|
466
|
+
initialValue: true,
|
|
467
|
+
}),
|
|
468
|
+
);
|
|
469
|
+
Sentry.setTag('continue-with-pod-install', continueWithPodInstall);
|
|
470
|
+
return continueWithPodInstall;
|
|
471
|
+
});
|
|
472
|
+
}
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
writeAppBuildGradle,
|
|
22
22
|
} from './gradle';
|
|
23
23
|
import { ReactNativeWizardOptions } from './options';
|
|
24
|
+
import { unPatchMetroConfig } from './metro';
|
|
24
25
|
|
|
25
26
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
26
27
|
const xcode = require('xcode');
|
|
@@ -36,6 +37,8 @@ export async function runReactNativeUninstall(
|
|
|
36
37
|
|
|
37
38
|
await confirmContinueIfNoOrDirtyGitRepo();
|
|
38
39
|
|
|
40
|
+
await unPatchMetroConfig();
|
|
41
|
+
|
|
39
42
|
unPatchXcodeFiles();
|
|
40
43
|
|
|
41
44
|
unPatchAndroidFiles();
|