@sentry/wizard 3.7.1 → 3.9.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.
Files changed (113) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/bin.ts +14 -0
  3. package/dist/bin.js +9 -0
  4. package/dist/bin.js.map +1 -1
  5. package/dist/lib/Helper/Logging.d.ts +1 -0
  6. package/dist/lib/Helper/Logging.js +2 -1
  7. package/dist/lib/Helper/Logging.js.map +1 -1
  8. package/dist/lib/Helper/__tests__/MergeConfig.js.map +1 -1
  9. package/dist/lib/Setup.js +4 -0
  10. package/dist/lib/Setup.js.map +1 -1
  11. package/dist/lib/Steps/ChooseIntegration.js +12 -26
  12. package/dist/lib/Steps/ChooseIntegration.js.map +1 -1
  13. package/dist/lib/Steps/Integrations/Cordova.js.map +1 -1
  14. package/dist/lib/Steps/Integrations/Electron.js.map +1 -1
  15. package/dist/lib/Steps/Integrations/MobileProject.js.map +1 -1
  16. package/dist/lib/Steps/Integrations/ReactNative.js +5 -5
  17. package/dist/lib/Steps/Integrations/ReactNative.js.map +1 -1
  18. package/dist/lib/Steps/Integrations/__tests__/ReactNative.js +5 -6
  19. package/dist/lib/Steps/Integrations/__tests__/ReactNative.js.map +1 -1
  20. package/dist/package.json +11 -7
  21. package/dist/src/apple/apple-wizard.js +31 -2
  22. package/dist/src/apple/apple-wizard.js.map +1 -1
  23. package/dist/src/apple/cocoapod.d.ts +2 -0
  24. package/dist/src/apple/cocoapod.js +122 -0
  25. package/dist/src/apple/cocoapod.js.map +1 -0
  26. package/dist/src/apple/code-tools.js +22 -12
  27. package/dist/src/apple/code-tools.js.map +1 -1
  28. package/dist/src/apple/fastlane.d.ts +2 -0
  29. package/dist/src/apple/fastlane.js +179 -0
  30. package/dist/src/apple/fastlane.js.map +1 -0
  31. package/dist/src/apple/templates.d.ts +1 -0
  32. package/dist/src/apple/templates.js +7 -3
  33. package/dist/src/apple/templates.js.map +1 -1
  34. package/dist/src/apple/xcode-manager.d.ts +1 -1
  35. package/dist/src/apple/xcode-manager.js +35 -28
  36. package/dist/src/apple/xcode-manager.js.map +1 -1
  37. package/dist/src/nextjs/nextjs-wizard.js +71 -81
  38. package/dist/src/nextjs/nextjs-wizard.js.map +1 -1
  39. package/dist/src/sourcemaps/sourcemaps-wizard.js +61 -46
  40. package/dist/src/sourcemaps/sourcemaps-wizard.js.map +1 -1
  41. package/dist/src/sourcemaps/tools/nextjs.d.ts +3 -0
  42. package/dist/src/sourcemaps/tools/nextjs.js +135 -0
  43. package/dist/src/sourcemaps/tools/nextjs.js.map +1 -0
  44. package/dist/src/sourcemaps/tools/sentry-cli.js +120 -16
  45. package/dist/src/sourcemaps/tools/sentry-cli.js.map +1 -1
  46. package/dist/src/sourcemaps/tools/vite.js +102 -12
  47. package/dist/src/sourcemaps/tools/vite.js.map +1 -1
  48. package/dist/src/sourcemaps/utils/detect-tool.d.ts +1 -1
  49. package/dist/src/sourcemaps/utils/detect-tool.js +1 -0
  50. package/dist/src/sourcemaps/utils/detect-tool.js.map +1 -1
  51. package/dist/src/sourcemaps/utils/other-wizards.js +35 -12
  52. package/dist/src/sourcemaps/utils/other-wizards.js.map +1 -1
  53. package/dist/src/sveltekit/sdk-setup.d.ts +9 -1
  54. package/dist/src/sveltekit/sdk-setup.js +73 -29
  55. package/dist/src/sveltekit/sdk-setup.js.map +1 -1
  56. package/dist/src/sveltekit/sveltekit-wizard.js +23 -13
  57. package/dist/src/sveltekit/sveltekit-wizard.js.map +1 -1
  58. package/dist/src/utils/ast-utils.d.ts +8 -0
  59. package/dist/src/utils/ast-utils.js +45 -0
  60. package/dist/src/utils/ast-utils.js.map +1 -0
  61. package/dist/src/utils/bash.d.ts +2 -1
  62. package/dist/src/utils/bash.js +14 -2
  63. package/dist/src/utils/bash.js.map +1 -1
  64. package/dist/src/utils/clack-utils.d.ts +7 -14
  65. package/dist/src/utils/clack-utils.js +46 -2
  66. package/dist/src/utils/clack-utils.js.map +1 -1
  67. package/dist/src/utils/debug.d.ts +2 -0
  68. package/dist/src/utils/debug.js +51 -0
  69. package/dist/src/utils/debug.js.map +1 -0
  70. package/dist/src/utils/package-json.d.ts +1 -1
  71. package/dist/src/utils/package-json.js.map +1 -1
  72. package/dist/src/utils/types.d.ts +24 -0
  73. package/dist/src/utils/types.js.map +1 -1
  74. package/dist/test/utils/ast-utils.test.d.ts +1 -0
  75. package/dist/test/utils/ast-utils.test.js +21 -0
  76. package/dist/test/utils/ast-utils.test.js.map +1 -0
  77. package/lib/Helper/Logging.ts +1 -1
  78. package/lib/Helper/__tests__/MergeConfig.ts +9 -4
  79. package/lib/Setup.ts +5 -0
  80. package/lib/Steps/ChooseIntegration.ts +13 -3
  81. package/lib/Steps/Integrations/Cordova.ts +3 -3
  82. package/lib/Steps/Integrations/Electron.ts +1 -2
  83. package/lib/Steps/Integrations/MobileProject.ts +1 -1
  84. package/lib/Steps/Integrations/ReactNative.ts +23 -17
  85. package/lib/Steps/Integrations/__tests__/ReactNative.ts +24 -15
  86. package/package.json +11 -7
  87. package/src/apple/apple-wizard.ts +35 -3
  88. package/src/apple/cocoapod.ts +57 -0
  89. package/src/apple/code-tools.ts +80 -57
  90. package/src/apple/fastlane.ts +160 -0
  91. package/src/apple/templates.ts +26 -10
  92. package/src/apple/xcode-manager.ts +137 -120
  93. package/src/nextjs/nextjs-wizard.ts +4 -13
  94. package/src/sourcemaps/sourcemaps-wizard.ts +40 -28
  95. package/src/sourcemaps/tools/nextjs.ts +114 -0
  96. package/src/sourcemaps/tools/sentry-cli.ts +134 -8
  97. package/src/sourcemaps/tools/vite.ts +101 -12
  98. package/src/sourcemaps/utils/detect-tool.ts +3 -1
  99. package/src/sourcemaps/utils/other-wizards.ts +32 -13
  100. package/src/sveltekit/sdk-setup.ts +122 -43
  101. package/src/sveltekit/sveltekit-wizard.ts +15 -6
  102. package/src/utils/ast-utils.ts +20 -0
  103. package/src/utils/bash.ts +43 -30
  104. package/src/utils/clack-utils.ts +42 -14
  105. package/src/utils/debug.ts +20 -0
  106. package/src/utils/package-json.ts +1 -1
  107. package/src/utils/types.ts +22 -0
  108. package/test/utils/ast-utils.test.ts +44 -0
  109. package/dist/src/sveltekit/sentry-cli-setup.d.ts +0 -2
  110. package/dist/src/sveltekit/sentry-cli-setup.js +0 -71
  111. package/dist/src/sveltekit/sentry-cli-setup.js.map +0 -1
  112. package/package-lock.json +0 -8910
  113. package/src/sveltekit/sentry-cli-setup.ts +0 -27
@@ -31,7 +31,6 @@ function configFileNames(num: number): {
31
31
  }
32
32
 
33
33
  describe('Merging next.config.js', () => {
34
-
35
34
  afterEach(() => {
36
35
  fs.unlinkSync(configPath);
37
36
  });
@@ -49,7 +48,9 @@ describe('Merging next.config.js', () => {
49
48
 
50
49
  mergeConfigFile(configPath, templatePath);
51
50
 
52
- expect(fs.readFileSync(configPath, 'utf8')).toEqual(fs.readFileSync(mergedPath, 'utf8'));
51
+ expect(fs.readFileSync(configPath, 'utf8')).toEqual(
52
+ fs.readFileSync(mergedPath, 'utf8'),
53
+ );
53
54
  });
54
55
 
55
56
  test('merge invalid javascript config return false', () => {
@@ -72,7 +73,9 @@ describe('Merging next.config.js', () => {
72
73
 
73
74
  mergeConfigFile(configPath, templatePath);
74
75
 
75
- expect(fs.readFileSync(configPath, 'utf8')).toEqual(fs.readFileSync(mergedPath, 'utf8'));
76
+ expect(fs.readFileSync(configPath, 'utf8')).toEqual(
77
+ fs.readFileSync(mergedPath, 'utf8'),
78
+ );
76
79
  });
77
80
 
78
81
  test('merge next.config.js with function return true', () => {
@@ -88,6 +91,8 @@ describe('Merging next.config.js', () => {
88
91
 
89
92
  mergeConfigFile(configPath, templatePath);
90
93
 
91
- expect(fs.readFileSync(configPath, 'utf8')).toEqual(fs.readFileSync(mergedPath, 'utf8'));
94
+ expect(fs.readFileSync(configPath, 'utf8')).toEqual(
95
+ fs.readFileSync(mergedPath, 'utf8'),
96
+ );
92
97
  });
93
98
  });
package/lib/Setup.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as _ from 'lodash';
2
+ import { enableDebugLogs } from '../src/utils/debug';
2
3
 
3
4
  import { readEnvironment } from './Helper/Env';
4
5
  import { startWizard } from './Helper/Wizard';
@@ -7,6 +8,10 @@ import * as Step from './Steps';
7
8
  export async function run(argv: any): Promise<any> {
8
9
  const args = { ...argv, ...readEnvironment() };
9
10
 
11
+ if (argv.debug) {
12
+ enableDebugLogs();
13
+ }
14
+
10
15
  if (args.uninstall === undefined) {
11
16
  args.uninstall = false;
12
17
  }
@@ -1,6 +1,5 @@
1
1
  import type { Answers } from 'inquirer';
2
2
  import { prompt } from 'inquirer';
3
- import * as _ from 'lodash';
4
3
  import { dim } from 'picocolors';
5
4
 
6
5
  import {
@@ -17,6 +16,7 @@ import { ReactNative } from './Integrations/ReactNative';
17
16
  import { SourceMapsShim } from './Integrations/SourceMapsShim';
18
17
  import { Apple } from './Integrations/Apple';
19
18
  import { SvelteKitShim } from './Integrations/SvelteKitShim';
19
+ import { hasPackageInstalled } from '../../src/utils/package-json';
20
20
 
21
21
  let projectPackage: any = {};
22
22
 
@@ -65,12 +65,22 @@ export class ChooseIntegration extends BaseStep {
65
65
  }
66
66
 
67
67
  public tryDetectingIntegration(): Integration | undefined {
68
- if (_.has(projectPackage, 'dependencies.react-native')) {
68
+ if (hasPackageInstalled('react-native', projectPackage)) {
69
69
  return Integration.reactNative;
70
70
  }
71
- if (_.has(projectPackage, 'dependencies.cordova')) {
71
+ if (hasPackageInstalled('cordova', projectPackage)) {
72
72
  return Integration.cordova;
73
73
  }
74
+ if (hasPackageInstalled('electron', projectPackage)) {
75
+ return Integration.electron;
76
+ }
77
+ if (hasPackageInstalled('next', projectPackage)) {
78
+ return Integration.nextjs;
79
+ }
80
+ if (hasPackageInstalled('@sveltejs/kit', projectPackage)) {
81
+ return Integration.sveltekit;
82
+ }
83
+
74
84
  return;
75
85
  }
76
86
 
@@ -12,6 +12,7 @@ const xcode = require('xcode');
12
12
 
13
13
  export class Cordova extends BaseIntegration {
14
14
  protected _sentryCli: SentryCli;
15
+
15
16
  protected _folderPrefix = 'platforms';
16
17
  protected _pluginFolder: string[] = ['.'];
17
18
 
@@ -25,9 +26,8 @@ export class Cordova extends BaseIntegration {
25
26
  return this.uninstall(answers);
26
27
  }
27
28
 
28
- const sentryCliProperties = this._sentryCli.convertAnswersToProperties(
29
- answers,
30
- );
29
+ const sentryCliProperties =
30
+ this._sentryCli.convertAnswersToProperties(answers);
31
31
 
32
32
  await patchMatchingFile(
33
33
  `${this._folderPrefix}/ios/*.xcodeproj/project.pbxproj`,
@@ -1,5 +1,5 @@
1
1
  import * as fs from 'fs';
2
- import type { Answers} from 'inquirer';
2
+ import type { Answers } from 'inquirer';
3
3
  import { prompt } from 'inquirer';
4
4
  import * as _ from 'lodash';
5
5
  import * as path from 'path';
@@ -21,7 +21,6 @@ Sentry.init({
21
21
  dsn: '___DSN___',
22
22
  });`;
23
23
 
24
-
25
24
  let appPackage: any = {};
26
25
 
27
26
  function printExample(example: string, title = ''): void {
@@ -1,4 +1,4 @@
1
- import type { Answers} from 'inquirer';
1
+ import type { Answers } from 'inquirer';
2
2
  import { prompt } from 'inquirer';
3
3
  import * as _ from 'lodash';
4
4
 
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable max-lines */
2
2
  import { exec } from 'child_process';
3
3
  import * as fs from 'fs';
4
- import type { Answers} from 'inquirer';
4
+ import type { Answers } from 'inquirer';
5
5
  import { prompt } from 'inquirer';
6
6
  import * as _ from 'lodash';
7
7
  import * as path from 'path';
@@ -44,7 +44,6 @@ export class ReactNative extends MobileProject {
44
44
  protected _answers: Answers;
45
45
  protected _sentryCli: SentryCli;
46
46
 
47
-
48
47
  public constructor(protected _argv: Args) {
49
48
  super(_argv);
50
49
  this.url = _argv.url;
@@ -71,7 +70,8 @@ export class ReactNative extends MobileProject {
71
70
  );
72
71
  if (!hasCompatibleReactNativeVersion && !this._argv.quiet) {
73
72
  userAnswers = await prompt({
74
- message: 'Your version of React Native is not compatible with Sentry\'s React Native SDK. Do you want to continue?',
73
+ message:
74
+ "Your version of React Native is not compatible with Sentry's React Native SDK. Do you want to continue?",
75
75
  name: 'continue',
76
76
  default: false,
77
77
  type: 'confirm',
@@ -111,9 +111,8 @@ export class ReactNative extends MobileProject {
111
111
  );
112
112
  }
113
113
 
114
- const sentryCliProperties = this._sentryCli.convertAnswersToProperties(
115
- answers,
116
- );
114
+ const sentryCliProperties =
115
+ this._sentryCli.convertAnswersToProperties(answers);
117
116
 
118
117
  const promises = this.getPlatforms(answers).map(
119
118
  async (platform: string) => {
@@ -146,17 +145,18 @@ export class ReactNative extends MobileProject {
146
145
 
147
146
  await Promise.all(promises);
148
147
 
149
- let host: string | null = null
148
+ let host: string | null = null;
150
149
  try {
151
- host = (new URL(this.url || '')).host;
150
+ host = new URL(this.url || '').host;
152
151
  } catch (_error) {
153
152
  // ignore
154
153
  }
155
154
  const orgSlug = _.get(answers, 'config.organization.slug', null);
156
155
  const projectId = _.get(answers, 'config.project.id', null);
157
- const projectIssuesUrl = host && orgSlug && projectId
158
- ? `https://${orgSlug}.${host}/issues/?project=${projectId}`
159
- : null;
156
+ const projectIssuesUrl =
157
+ host && orgSlug && projectId
158
+ ? `https://${orgSlug}.${host}/issues/?project=${projectId}`
159
+ : null;
160
160
 
161
161
  l(`
162
162
  To make sure everything is set up correctly, put the following code snippet into your application.
@@ -169,7 +169,9 @@ The snippet will create a button that, when tapped, sends a test event to Sentry
169
169
  nl();
170
170
  }
171
171
 
172
- l(`<Button title='Try!' onPress={ () => { Sentry.captureException(new Error('First error')) }}/>`);
172
+ l(
173
+ `<Button title='Try!' onPress={ () => { Sentry.captureException(new Error('First error')) }}/>`,
174
+ );
173
175
  nl();
174
176
 
175
177
  if (!this._argv.quiet) {
@@ -337,7 +339,7 @@ The snippet will create a button that, when tapped, sends a test event to Sentry
337
339
  return Promise.resolve(
338
340
  contents.replace(
339
341
  /^([^]*)(import\s+[^;]*?;$)/m,
340
- match =>
342
+ (match) =>
341
343
  // eslint-disable-next-line prefer-template
342
344
  match +
343
345
  "\n\nimport * as Sentry from '@sentry/react-native';\n\n" +
@@ -361,7 +363,7 @@ The snippet will create a button that, when tapped, sends a test event to Sentry
361
363
  contents.replace(
362
364
  ReactNative._buildGradleAndroidSectionBeginning,
363
365
  // eslint-disable-next-line prefer-template
364
- match => applyFrom + '\n' + match,
366
+ (match) => applyFrom + '\n' + match,
365
367
  ),
366
368
  );
367
369
  }
@@ -396,7 +398,7 @@ The snippet will create a button that, when tapped, sends a test event to Sentry
396
398
  // eslint-disable-next-line no-useless-escape
397
399
  '\\"../node_modules/@sentry/cli/bin/sentry-cli react-native xcode $REACT_NATIVE_XCODE\\"',
398
400
  ) +
399
- '\n/bin/sh ../node_modules/@sentry/react-native/scripts/collect-modules.sh\n';
401
+ '\n/bin/sh -c "$WITH_ENVIRONMENT ../node_modules/@sentry/react-native/scripts/collect-modules.sh"\n';
400
402
  script.shellScript = JSON.stringify(code);
401
403
  }
402
404
  }
@@ -420,8 +422,12 @@ The snippet will create a button that, when tapped, sends a test event to Sentry
420
422
  {
421
423
  shellPath: '/bin/sh',
422
424
  shellScript: `
425
+ WITH_ENVIRONMENT="../node_modules/react-native/scripts/xcode/with-environment.sh"
426
+ if [ -f "$WITH_ENVIRONMENT" ]; then
427
+ . "$WITH_ENVIRONMENT"
428
+ fi
423
429
  export SENTRY_PROPERTIES=sentry.properties
424
- [[ $SENTRY_INCLUDE_NATIVE_SOURCES == "true" ]] && INCLUDE_SOURCES_FLAG="--include-sources" || INCLUDE_SOURCES_FLAG=""
430
+ [ "$SENTRY_INCLUDE_NATIVE_SOURCES" = "true" ] && INCLUDE_SOURCES_FLAG="--include-sources" || INCLUDE_SOURCES_FLAG=""
425
431
  ../node_modules/@sentry/cli/bin/sentry-cli debug-files upload "$INCLUDE_SOURCES_FLAG" "$DWARF_DSYM_FOLDER_PATH"
426
432
  `,
427
433
  },
@@ -507,7 +513,7 @@ export SENTRY_PROPERTIES=sentry.properties
507
513
  // remove sentry properties export
508
514
  .replace(/^export SENTRY_PROPERTIES=sentry.properties\r?\n/m, '')
509
515
  .replace(
510
- /^\/bin\/sh ..\/node_modules\/@sentry\/react-native\/scripts\/collect-modules.sh\r?\n/m,
516
+ /^\/bin\/sh .*?..\/node_modules\/@sentry\/react-native\/scripts\/collect-modules.sh"?\r?\n/m,
511
517
  '',
512
518
  )
513
519
  // unwrap react-native-xcode.sh command. In case someone replaced it
@@ -7,7 +7,7 @@ import * as path from 'path';
7
7
  import * as process from 'process';
8
8
  import * as rimraf from 'rimraf';
9
9
 
10
- import type { Args} from '../../../Constants';
10
+ import type { Args } from '../../../Constants';
11
11
  import { Integration, Platform } from '../../../Constants';
12
12
  import { ReactNative } from '../ReactNative';
13
13
 
@@ -18,7 +18,8 @@ const appBuildGradle = 'android/app/build.gradle';
18
18
  const yarnLock = 'yarn.lock';
19
19
 
20
20
  const dummyJsContent = 'import React from "react";\n';
21
- const dummyAppBuildGradleContent = 'apply plugin: "com.facebook.react"\n\nandroid {\n}\n';
21
+ const dummyAppBuildGradleContent =
22
+ 'apply plugin: "com.facebook.react"\n\nandroid {\n}\n';
22
23
 
23
24
  const testArgs = {
24
25
  debug: false,
@@ -31,7 +32,7 @@ const testArgs = {
31
32
  };
32
33
 
33
34
  const mockIosAnswers: Answers = {
34
- shouldConfigurePlatforms: { 'ios': true },
35
+ shouldConfigurePlatforms: { ios: true },
35
36
  config: {
36
37
  dsn: {
37
38
  public: 'dns.public.com',
@@ -40,7 +41,7 @@ const mockIosAnswers: Answers = {
40
41
  };
41
42
 
42
43
  const mockAndroidAnswers: Answers = {
43
- shouldConfigurePlatforms: { 'android': true },
44
+ shouldConfigurePlatforms: { android: true },
44
45
  config: {
45
46
  dsn: {
46
47
  public: 'dns.public.com',
@@ -52,15 +53,15 @@ const originalExec = child_process.exec;
52
53
 
53
54
  const restoreExec = (): void => {
54
55
  (child_process as any).exec = originalExec;
55
- }
56
+ };
56
57
 
57
58
  const mockExec = (): void => {
58
- (child_process.exec as unknown as jest.Mock)
59
- .mockImplementation((_command, callback) => callback(null, { stdout: '' }));
60
- }
59
+ (child_process.exec as unknown as jest.Mock).mockImplementation(
60
+ (_command, callback) => callback(null, { stdout: '' }),
61
+ );
62
+ };
61
63
 
62
64
  describe('ReactNative', () => {
63
-
64
65
  const defaultCwd = process.cwd();
65
66
 
66
67
  beforeEach(() => {
@@ -88,10 +89,11 @@ describe('ReactNative', () => {
88
89
 
89
90
  const patchedIosIndexJs = fs.readFileSync(iosIndexJs, 'utf8');
90
91
  const patchedAppTsx = fs.readFileSync(appTsx, 'utf8');
91
- const expectedPatch = 'import React from "react";\n\n' +
92
- 'import * as Sentry from \'@sentry/react-native\';\n\n' +
92
+ const expectedPatch =
93
+ 'import React from "react";\n\n' +
94
+ "import * as Sentry from '@sentry/react-native';\n\n" +
93
95
  'Sentry.init({ \n' +
94
- ' dsn: \'dns.public.com\', \n' +
96
+ " dsn: 'dns.public.com', \n" +
95
97
  '});\n\n';
96
98
  expect(patchedIosIndexJs).toEqual(expectedPatch);
97
99
  expect(patchedAppTsx).toEqual(expectedPatch);
@@ -103,7 +105,8 @@ describe('ReactNative', () => {
103
105
  await project.emit(mockAndroidAnswers);
104
106
 
105
107
  const patchedAppBuildGradle = fs.readFileSync(appBuildGradle, 'utf8');
106
- const expectedPatch = 'apply plugin: "com.facebook.react"\n\n' +
108
+ const expectedPatch =
109
+ 'apply plugin: "com.facebook.react"\n\n' +
107
110
  'apply from: "../../node_modules/@sentry/react-native/sentry.gradle"\n' +
108
111
  'android {\n}\n';
109
112
  expect(patchedAppBuildGradle).toEqual(expectedPatch);
@@ -114,7 +117,10 @@ describe('ReactNative', () => {
114
117
 
115
118
  await project.emit(mockIosAnswers);
116
119
 
117
- expect(child_process.exec).toHaveBeenCalledWith('yarn add @sentry/react-native', expect.anything());
120
+ expect(child_process.exec).toHaveBeenCalledWith(
121
+ 'yarn add @sentry/react-native',
122
+ expect.anything(),
123
+ );
118
124
  });
119
125
 
120
126
  test('executes pod install', async () => {
@@ -122,6 +128,9 @@ describe('ReactNative', () => {
122
128
 
123
129
  await project.emit(mockIosAnswers);
124
130
 
125
- expect(child_process.exec).toHaveBeenCalledWith('npx --yes pod-install --non-interactive --quiet', expect.anything());
131
+ expect(child_process.exec).toHaveBeenCalledWith(
132
+ 'npx --yes pod-install --non-interactive --quiet',
133
+ expect.anything(),
134
+ );
126
135
  });
127
136
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/wizard",
3
- "version": "3.7.1",
3
+ "version": "3.9.0",
4
4
  "homepage": "https://github.com/getsentry/sentry-wizard",
5
5
  "repository": "https://github.com/getsentry/sentry-wizard",
6
6
  "description": "Sentry wizard helping you to configure your project",
@@ -32,7 +32,7 @@
32
32
  "glob": "^7.1.3",
33
33
  "inquirer": "^6.2.0",
34
34
  "lodash": "^4.17.15",
35
- "magicast": "^0.2.9",
35
+ "magicast": "^0.2.10",
36
36
  "opn": "^5.4.0",
37
37
  "r2": "^2.0.1",
38
38
  "read-env": "^1.3.0",
@@ -65,7 +65,7 @@
65
65
  "**/xmldom": "^0.6.0"
66
66
  },
67
67
  "engines": {
68
- "node": ">=14.0.0",
68
+ "node": ">=14.18.0",
69
69
  "npm": ">=3.10.7",
70
70
  "yarn": ">=1.0.2"
71
71
  },
@@ -76,15 +76,15 @@
76
76
  "build": "yarn tsc",
77
77
  "postbuild": "chmod +x ./dist/bin.js && cp -r scripts/** dist",
78
78
  "lint": "yarn lint:prettier && yarn lint:eslint",
79
- "lint:prettier": "prettier --check lib/**/*.ts",
79
+ "lint:prettier": "prettier --check \"{lib,src,test}/**/*.ts\"",
80
80
  "lint:eslint": "eslint . --cache --format stylish",
81
81
  "fix": "yarn fix:eslint && yarn fix:prettier",
82
- "fix:prettier": "prettier --write lib/**/*.ts",
82
+ "fix:prettier": "prettier --write \"{lib,src,test}/**/*.ts\"",
83
83
  "fix:eslint": "eslint . --format stylish --fix",
84
84
  "test": "yarn build && jest",
85
85
  "try": "ts-node bin.ts",
86
86
  "try:uninstall": "ts-node bin.ts --uninstall",
87
- "test:watch": "jest --watch --notify"
87
+ "test:watch": "jest --watch"
88
88
  },
89
89
  "jest": {
90
90
  "collectCoverage": true,
@@ -114,5 +114,9 @@
114
114
  "testEnvironment": "node"
115
115
  },
116
116
  "author": "Sentry",
117
- "license": "MIT"
117
+ "license": "MIT",
118
+ "volta": {
119
+ "node": "14.18.3",
120
+ "yarn": "1.22.19"
121
+ }
118
122
  }
@@ -10,9 +10,11 @@ import * as path from 'path';
10
10
  import * as xcManager from './xcode-manager';
11
11
  import * as codeTools from './code-tools';
12
12
  import * as bash from '../utils/bash';
13
- import { WizardOptions } from '../utils/types';
13
+ import { SentryProjectData, WizardOptions } from '../utils/types';
14
14
  import * as Sentry from '@sentry/node';
15
15
  import { traceStep, withTelemetry } from '../telemetry';
16
+ import * as cocoapod from './cocoapod';
17
+ import * as fastlane from './fastlane';
16
18
 
17
19
  const xcode = require('xcode');
18
20
  /* eslint-enable @typescript-eslint/no-unused-vars */
@@ -22,7 +24,6 @@ import {
22
24
  askForSelfHosted,
23
25
  askForWizardLogin,
24
26
  askToInstallSentryCLI,
25
- SentryProjectData,
26
27
  printWelcome,
27
28
  abort,
28
29
  askForItemSelection,
@@ -102,10 +103,24 @@ async function runAppleWizardWithTelementry(
102
103
  options.url,
103
104
  );
104
105
 
106
+ const hasCocoa = cocoapod.usesCocoaPod(projectDir);
107
+
108
+ if (hasCocoa) {
109
+ const podAdded = await traceStep('Add CocoaPods reference', () =>
110
+ cocoapod.addCocoaPods(projectDir),
111
+ );
112
+ if (!podAdded) {
113
+ clack.log.warn(
114
+ "Could not add Sentry pod to your Podfile. You'll have to add it manually.\nPlease follow the instructions at https://docs.sentry.io/platforms/apple/guides/ios/#install",
115
+ );
116
+ }
117
+ }
118
+
105
119
  traceStep('Update Xcode project', () => {
106
- xcManager.updateXcodeProject(pbxproj, project, apiKey, true, true);
120
+ xcManager.updateXcodeProject(pbxproj, project, apiKey, !hasCocoa, true);
107
121
  });
108
122
 
123
+ Sentry.setTag('package-manager', hasCocoa ? 'cocoapods' : 'SPM');
109
124
  const projSource = path.join(
110
125
  projectDir,
111
126
  xcodeProjFile.replace('.xcodeproj', ''),
@@ -123,6 +138,23 @@ async function runAppleWizardWithTelementry(
123
138
  return;
124
139
  }
125
140
 
141
+ if (fastlane.fastFile(projectDir)) {
142
+ const addLane = await clack.confirm({
143
+ message:
144
+ 'Found a Fastfile in your project. Do you want to configure a lane to upload debug symbols to Sentry?',
145
+ });
146
+ if (addLane) {
147
+ await traceStep('Configure fastlane', () =>
148
+ fastlane.addSentryToFastlane(
149
+ projectDir,
150
+ project.organization.slug,
151
+ project.slug,
152
+ apiKey.token,
153
+ ),
154
+ );
155
+ }
156
+ }
157
+
126
158
  clack.log.success('Sentry was successfully added to your project!');
127
159
  }
128
160
 
@@ -0,0 +1,57 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import * as bash from '../utils/bash';
4
+ import * as Sentry from '@sentry/node';
5
+ // @ts-ignore - clack is ESM and TS complains about that. It works though
6
+ import * as clack from '@clack/prompts';
7
+
8
+ export function usesCocoaPod(projPath: string): boolean {
9
+ return fs.existsSync(path.join(projPath, 'Podfile'));
10
+ }
11
+
12
+ export async function addCocoaPods(projPath: string): Promise<boolean> {
13
+ const podfile = path.join(projPath, 'Podfile');
14
+
15
+ const podContent = fs.readFileSync(podfile, 'utf8');
16
+
17
+ if (
18
+ /^\s*pod\s+(['"]Sentry['"]|['"]SentrySwiftUI['"])\s*$/im.test(podContent)
19
+ ) {
20
+ // Already have Sentry pod
21
+ return true;
22
+ }
23
+
24
+ let podMatch = /^( *)pod\s+['"](\w+)['"] *$/im.exec(podContent);
25
+ if (!podMatch) {
26
+ // No Podfile is empty, will try to add Sentry pod after "use_frameworks!"
27
+ const frameworkMatch = /^( *)use_frameworks![^\n]* *$/im.exec(podContent);
28
+ if (!frameworkMatch) {
29
+ return false;
30
+ }
31
+ podMatch = frameworkMatch;
32
+ }
33
+
34
+ const insertIndex = podMatch.index + podMatch[0].length;
35
+ const newFileContent =
36
+ podContent.slice(0, insertIndex) +
37
+ '\n' +
38
+ podMatch[1] +
39
+ "pod 'Sentry'\n" +
40
+ podContent.slice(insertIndex);
41
+ fs.writeFileSync(podfile, newFileContent, 'utf8');
42
+
43
+ const loginSpinner = clack.spinner();
44
+
45
+ loginSpinner.start("Running 'pod install'. This may take a few minutes...");
46
+
47
+ try {
48
+ await bash.execute('pod install --silent');
49
+ loginSpinner.stop('Sentry pod added to the project.');
50
+ } catch (e) {
51
+ clack.log.error("'pod install' failed. You will need to run it manually.");
52
+ loginSpinner.stop();
53
+ Sentry.captureException('Sentry pod install failed.');
54
+ }
55
+
56
+ return true;
57
+ }