@sentry/wizard 3.4.0 → 3.6.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 (122) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/bin.ts +29 -20
  3. package/dist/bin.js +27 -19
  4. package/dist/bin.js.map +1 -1
  5. package/dist/lib/Constants.d.ts +2 -0
  6. package/dist/lib/Constants.js +5 -0
  7. package/dist/lib/Constants.js.map +1 -1
  8. package/dist/lib/Helper/Wizard.js +2 -9
  9. package/dist/lib/Helper/Wizard.js.map +1 -1
  10. package/dist/lib/Helper/__tests__/SentryCli.js +1 -0
  11. package/dist/lib/Helper/__tests__/SentryCli.js.map +1 -1
  12. package/dist/lib/Setup.js.map +1 -1
  13. package/dist/lib/Steps/ChooseIntegration.js +30 -10
  14. package/dist/lib/Steps/ChooseIntegration.js.map +1 -1
  15. package/dist/lib/Steps/Integrations/Apple.d.ts +10 -0
  16. package/dist/lib/Steps/Integrations/Apple.js +92 -0
  17. package/dist/lib/Steps/Integrations/Apple.js.map +1 -0
  18. package/dist/lib/Steps/Integrations/{NextJs.d.ts → NextJsShim.d.ts} +1 -1
  19. package/dist/lib/Steps/Integrations/{NextJs.js → NextJsShim.js} +14 -10
  20. package/dist/lib/Steps/Integrations/NextJsShim.js.map +1 -0
  21. package/dist/lib/Steps/Integrations/SourceMapsShim.js +5 -1
  22. package/dist/lib/Steps/Integrations/SourceMapsShim.js.map +1 -1
  23. package/dist/lib/Steps/Integrations/{SvelteKit.d.ts → SvelteKitShim.d.ts} +1 -1
  24. package/dist/lib/Steps/Integrations/{SvelteKit.js → SvelteKitShim.js} +14 -10
  25. package/dist/lib/Steps/Integrations/SvelteKitShim.js.map +1 -0
  26. package/dist/package.json +4 -4
  27. package/dist/src/apple/apple-wizard.d.ts +2 -0
  28. package/dist/src/apple/apple-wizard.js +197 -0
  29. package/dist/src/apple/apple-wizard.js.map +1 -0
  30. package/dist/src/apple/code-tools.d.ts +1 -0
  31. package/dist/src/apple/code-tools.js +100 -0
  32. package/dist/src/apple/code-tools.js.map +1 -0
  33. package/dist/src/apple/templates.d.ts +4 -0
  34. package/dist/src/apple/templates.js +19 -0
  35. package/dist/src/apple/templates.js.map +1 -0
  36. package/dist/src/apple/xcode-manager.d.ts +4 -0
  37. package/dist/src/apple/xcode-manager.js +145 -0
  38. package/dist/src/apple/xcode-manager.js.map +1 -0
  39. package/dist/src/nextjs/nextjs-wizard.d.ts +2 -5
  40. package/dist/src/nextjs/nextjs-wizard.js +1 -1
  41. package/dist/src/nextjs/nextjs-wizard.js.map +1 -1
  42. package/dist/src/sourcemaps/sourcemaps-wizard.d.ts +2 -5
  43. package/dist/src/sourcemaps/sourcemaps-wizard.js +154 -67
  44. package/dist/src/sourcemaps/sourcemaps-wizard.js.map +1 -1
  45. package/dist/src/sourcemaps/tools/angular.d.ts +1 -0
  46. package/dist/src/sourcemaps/tools/angular.js +76 -0
  47. package/dist/src/sourcemaps/tools/angular.js.map +1 -0
  48. package/dist/src/sourcemaps/tools/create-react-app.d.ts +1 -0
  49. package/dist/src/sourcemaps/tools/create-react-app.js +69 -0
  50. package/dist/src/sourcemaps/tools/create-react-app.js.map +1 -0
  51. package/dist/src/sourcemaps/tools/esbuild.js +2 -1
  52. package/dist/src/sourcemaps/tools/esbuild.js.map +1 -1
  53. package/dist/src/sourcemaps/tools/rollup.js +2 -1
  54. package/dist/src/sourcemaps/tools/rollup.js.map +1 -1
  55. package/dist/src/sourcemaps/tools/sentry-cli.js +7 -1
  56. package/dist/src/sourcemaps/tools/sentry-cli.js.map +1 -1
  57. package/dist/src/sourcemaps/tools/vite.js +2 -1
  58. package/dist/src/sourcemaps/tools/vite.js.map +1 -1
  59. package/dist/src/sourcemaps/tools/webpack.js +2 -1
  60. package/dist/src/sourcemaps/tools/webpack.js.map +1 -1
  61. package/dist/src/sourcemaps/utils/detect-tool.d.ts +3 -0
  62. package/dist/src/sourcemaps/utils/detect-tool.js +73 -0
  63. package/dist/src/sourcemaps/utils/detect-tool.js.map +1 -0
  64. package/dist/src/sourcemaps/utils/other-wizards.d.ts +4 -0
  65. package/dist/src/sourcemaps/utils/other-wizards.js +183 -0
  66. package/dist/src/sourcemaps/utils/other-wizards.js.map +1 -0
  67. package/dist/src/sourcemaps/utils/sdk-version.d.ts +14 -0
  68. package/dist/src/sourcemaps/utils/sdk-version.js +276 -0
  69. package/dist/src/sourcemaps/utils/sdk-version.js.map +1 -0
  70. package/dist/src/sveltekit/sveltekit-wizard.d.ts +2 -5
  71. package/dist/src/sveltekit/sveltekit-wizard.js +3 -2
  72. package/dist/src/sveltekit/sveltekit-wizard.js.map +1 -1
  73. package/dist/src/telemetry.d.ts +1 -0
  74. package/dist/src/telemetry.js +7 -1
  75. package/dist/src/telemetry.js.map +1 -1
  76. package/dist/src/utils/bash.d.ts +3 -0
  77. package/dist/src/utils/bash.js +118 -0
  78. package/dist/src/utils/bash.js.map +1 -0
  79. package/dist/src/utils/clack-utils.d.ts +21 -9
  80. package/dist/src/utils/clack-utils.js +122 -56
  81. package/dist/src/utils/clack-utils.js.map +1 -1
  82. package/dist/src/utils/package-json.d.ts +19 -0
  83. package/dist/src/utils/package-json.js +29 -0
  84. package/dist/src/utils/package-json.js.map +1 -0
  85. package/dist/src/utils/types.d.ts +16 -0
  86. package/dist/src/utils/types.js +3 -0
  87. package/dist/src/utils/types.js.map +1 -0
  88. package/lib/Constants.ts +6 -0
  89. package/lib/Helper/Wizard.ts +3 -9
  90. package/lib/Helper/__tests__/SentryCli.ts +2 -1
  91. package/lib/Setup.ts +1 -0
  92. package/lib/Steps/ChooseIntegration.ts +39 -11
  93. package/lib/Steps/Integrations/Apple.ts +27 -0
  94. package/lib/Steps/Integrations/{NextJs.ts → NextJsShim.ts} +6 -2
  95. package/lib/Steps/Integrations/SourceMapsShim.ts +5 -1
  96. package/lib/Steps/Integrations/{SvelteKit.ts → SvelteKitShim.ts} +6 -2
  97. package/package-lock.json +8910 -0
  98. package/package.json +4 -4
  99. package/src/apple/apple-wizard.ts +150 -0
  100. package/src/apple/code-tools.ts +81 -0
  101. package/src/apple/templates.ts +39 -0
  102. package/src/apple/xcode-manager.ts +147 -0
  103. package/src/nextjs/nextjs-wizard.ts +3 -8
  104. package/src/sourcemaps/sourcemaps-wizard.ts +166 -91
  105. package/src/sourcemaps/tools/angular.ts +42 -0
  106. package/src/sourcemaps/tools/create-react-app.ts +19 -0
  107. package/src/sourcemaps/tools/esbuild.ts +1 -1
  108. package/src/sourcemaps/tools/rollup.ts +2 -4
  109. package/src/sourcemaps/tools/sentry-cli.ts +6 -1
  110. package/src/sourcemaps/tools/vite.ts +1 -1
  111. package/src/sourcemaps/tools/webpack.ts +1 -1
  112. package/src/sourcemaps/utils/detect-tool.ts +41 -0
  113. package/src/sourcemaps/utils/other-wizards.ts +148 -0
  114. package/src/sourcemaps/utils/sdk-version.ts +257 -0
  115. package/src/sveltekit/sveltekit-wizard.ts +4 -7
  116. package/src/telemetry.ts +8 -0
  117. package/src/utils/bash.ts +44 -0
  118. package/src/utils/clack-utils.ts +100 -51
  119. package/src/utils/package-json.ts +45 -0
  120. package/src/utils/types.ts +18 -0
  121. package/dist/lib/Steps/Integrations/NextJs.js.map +0 -1
  122. package/dist/lib/Steps/Integrations/SvelteKit.js.map +0 -1
@@ -0,0 +1,257 @@
1
+ // @ts-ignore - clack is ESM and TS complains about that. It works though
2
+ import clack from '@clack/prompts';
3
+ import chalk from 'chalk';
4
+ import { minVersion, satisfies } from 'semver';
5
+ import {
6
+ abortIfCancelled,
7
+ getPackageDotJson,
8
+ installPackage,
9
+ } from '../../utils/clack-utils';
10
+
11
+ import * as Sentry from '@sentry/node';
12
+ import { findInstalledPackageFromList } from '../../utils/package-json';
13
+
14
+ const MINIMUM_DEBUG_ID_SDK_VERSION = '7.47.0';
15
+
16
+ // This array is orderd by the SDKs we want to check for first.
17
+ // The reason is that some SDKs depend on others and some users might
18
+ // have added the dependencies to their package.json. We want to make sure
19
+ // that we actually detect the "top-level" SDK first.
20
+ const SENTRY_SDK_PACKAGE_NAMES = [
21
+ // SDKs using other framework SDKs need to be checked first
22
+ '@sentry/gatsby',
23
+ '@sentry/nextjs',
24
+ '@sentry/remix',
25
+ '@sentry/sveltekit',
26
+
27
+ // Framework SDKs
28
+ '@sentry/angular',
29
+ '@sentry/angular-ivy',
30
+ '@sentry/ember',
31
+ '@sentry/react',
32
+ '@sentry/svelte',
33
+ '@sentry/vue',
34
+ '@sentry/serverless',
35
+
36
+ // Base SDKs
37
+ '@sentry/browser',
38
+ '@sentry/node',
39
+ ];
40
+
41
+ /**
42
+ * Check for a minimum SDK version and prompt the user to upgrade if necessary.
43
+ * We distinguish between 4 cases here:
44
+ *
45
+ * 1. Users didn't install any SDK yet
46
+ * -> We tell them to install an SDK and then continue with the wizard
47
+ * 2. Users installed an SDK in the range >=7.47.0
48
+ * -> All good, no need to do anything!
49
+ * 3. Users installed an SDK in the range >=7.0.0 <= 7.46.0
50
+ * -> We ask if they want to auto-update to the latest version
51
+ * 4. Users installed an SDK in the range <7.x
52
+ * -> We tell users to manually upgrade (migrate between majors)
53
+ */
54
+ export async function ensureMinimumSdkVersionIsInstalled(): Promise<void> {
55
+ const installedSdkPackage = findInstalledPackageFromList(
56
+ SENTRY_SDK_PACKAGE_NAMES,
57
+ await getPackageDotJson(),
58
+ );
59
+
60
+ // Case 1:
61
+ if (!installedSdkPackage) {
62
+ return await handleNoSdkInstalled();
63
+ }
64
+
65
+ const { name: installedSdkName, version: installedSdkVersionOrRange } =
66
+ installedSdkPackage;
67
+
68
+ Sentry.setTag('installed-sdk', installedSdkName);
69
+
70
+ const minInstalledVersion = getMinInstalledVersion(
71
+ installedSdkVersionOrRange,
72
+ installedSdkName,
73
+ );
74
+
75
+ if (!minInstalledVersion) {
76
+ // This is handled in the getMinInstalledVersion function
77
+ return;
78
+ }
79
+
80
+ const hasDebugIdCompatibleSdkVersion = satisfies(
81
+ minInstalledVersion,
82
+ `>=${MINIMUM_DEBUG_ID_SDK_VERSION}`,
83
+ );
84
+
85
+ // Case 2:
86
+ if (hasDebugIdCompatibleSdkVersion) {
87
+ Sentry.setTag('initial-sdk-version', '>=7.47.0');
88
+ return;
89
+ }
90
+
91
+ const hasV7SdkVersion = satisfies(minInstalledVersion, '>=7.0.0');
92
+
93
+ clack.log.warn(
94
+ `${chalk.yellowBright(
95
+ `It seems like you're using an outdated version (${installedSdkVersionOrRange}) of the ${chalk.bold(
96
+ installedSdkName,
97
+ )} SDK.`,
98
+ )}
99
+ Uploading source maps is easiest with an SDK from version ${chalk.bold(
100
+ MINIMUM_DEBUG_ID_SDK_VERSION,
101
+ )} or newer.
102
+ `,
103
+ );
104
+
105
+ // Case 3:
106
+ if (hasV7SdkVersion) {
107
+ await handleAutoUpdateSdk(installedSdkName);
108
+ return;
109
+ }
110
+
111
+ // Case 4:
112
+ await handleManuallyUpdateSdk(minInstalledVersion);
113
+ }
114
+
115
+ async function handleManuallyUpdateSdk(minInstalledVersion: string) {
116
+ Sentry.setTag(
117
+ 'initial-sdk-version',
118
+ `${satisfies(minInstalledVersion, '>=6.0.0') ? '6.x' : '<6.0.0'}`,
119
+ );
120
+
121
+ clack.log
122
+ .info(`When upgrading from a version older than 7.0.0, make sure to follow the migration guide:
123
+ https://github.com/getsentry/sentry-javascript/blob/develop/MIGRATION.md#upgrading-from-6x-to-7x
124
+ `);
125
+
126
+ const didUpdate = await abortIfCancelled(
127
+ clack.select({
128
+ message: 'Did you update your SDK to the latest version?',
129
+ options: [
130
+ {
131
+ label: 'Yes!',
132
+ value: true,
133
+ },
134
+ {
135
+ label: "No, I'll do it later...",
136
+ value: false,
137
+ hint: chalk.yellow(
138
+ `Remember to update your SDK to at least ${MINIMUM_DEBUG_ID_SDK_VERSION}.`,
139
+ ),
140
+ },
141
+ ],
142
+ initialValue: true,
143
+ }),
144
+ );
145
+
146
+ Sentry.setTag(
147
+ 'resolved-sdk-status',
148
+ didUpdate ? 'updated-manually' : 'update-later',
149
+ );
150
+ }
151
+
152
+ async function handleAutoUpdateSdk(packageName: string) {
153
+ Sentry.setTag('initial-sdk-version', '>=7.0.0 <7.47.0');
154
+
155
+ const shouldUpdate = await abortIfCancelled(
156
+ clack.select({
157
+ message:
158
+ 'Do you want to automatically update your SDK to the latest version?',
159
+ options: [
160
+ {
161
+ label: 'Yes!',
162
+ value: true,
163
+ hint: chalk.green('Recommended'),
164
+ },
165
+ {
166
+ label: "No, I'll do it later...",
167
+ value: false,
168
+ hint: chalk.yellow(
169
+ `Remember to update your SDK to at least ${MINIMUM_DEBUG_ID_SDK_VERSION}.`,
170
+ ),
171
+ },
172
+ ],
173
+ initialValue: true,
174
+ }),
175
+ );
176
+
177
+ if (shouldUpdate) {
178
+ await installPackage({
179
+ packageName,
180
+ alreadyInstalled: true,
181
+ askBeforeUpdating: false, // we already did this above
182
+ });
183
+ }
184
+
185
+ Sentry.setTag(
186
+ 'resolved-sdk-status',
187
+ shouldUpdate ? 'updated-automatically' : 'update-later',
188
+ );
189
+ }
190
+
191
+ async function handleNoSdkInstalled(): Promise<void> {
192
+ Sentry.setTag('initial-sdk-version', 'none');
193
+ Sentry.setTag('installed-sdk', 'none');
194
+
195
+ clack.log.warn(
196
+ `${chalk.yellowBright(
197
+ `It seems like you didn't yet install a Sentry SDK in your project.`,
198
+ )}
199
+ We recommend setting up the SDK before continuing with the source maps wizard.
200
+
201
+ ${chalk.dim(`Take a look at our docs to get started:
202
+ https://docs.sentry.io/`)}`,
203
+ );
204
+
205
+ const installedSDK = await abortIfCancelled(
206
+ clack.select({
207
+ message: 'Did you set up your Sentry SDK?',
208
+ options: [
209
+ { label: 'Yes, continue!', value: true },
210
+ {
211
+ label: "I'll do it later...",
212
+ value: false,
213
+ hint: chalk.yellow(
214
+ 'You need to set up an SDK before you can use Sentry',
215
+ ),
216
+ },
217
+ ],
218
+ initialValue: true,
219
+ }),
220
+ );
221
+
222
+ Sentry.setTag(
223
+ 'resolved-sdk-status',
224
+ installedSDK ? 'installed-manually' : 'install-later',
225
+ );
226
+ }
227
+
228
+ function getMinInstalledVersion(
229
+ installedSdkVersionOrRange: string,
230
+ installedSdkName: string,
231
+ ): string | undefined {
232
+ try {
233
+ // If `minVersion` is unable to parse the version it will throw an error
234
+ // However, it will also return `null` if the parameter is undefined, which
235
+ // we explicitly checked before but the typing doesn't know that.
236
+ const minInstalledVersion = minVersion(installedSdkVersionOrRange)?.version;
237
+ if (minInstalledVersion) {
238
+ return minInstalledVersion;
239
+ }
240
+ } catch {
241
+ // handling this, along with the `null` case below
242
+ }
243
+
244
+ Sentry.setTag('initial-sdk-version', 'unknown');
245
+ clack.log.warn(
246
+ `${chalk.yellow(
247
+ `Could not parse the version of your installed SDK ("${installedSdkName}": "${installedSdkVersionOrRange}")`,
248
+ )}
249
+
250
+ Please make sure that your Sentry SDK is updated to version ${chalk.bold(
251
+ MINIMUM_DEBUG_ID_SDK_VERSION,
252
+ )} or newer.
253
+ `,
254
+ );
255
+
256
+ return undefined;
257
+ }
@@ -9,21 +9,18 @@ import {
9
9
  confirmContinueEvenThoughNoGitRepo,
10
10
  ensurePackageIsInstalled,
11
11
  getPackageDotJson,
12
- hasPackageInstalled,
13
12
  installPackage,
14
13
  printWelcome,
15
14
  } from '../utils/clack-utils';
15
+ import { hasPackageInstalled } from '../utils/package-json';
16
+ import { WizardOptions } from '../utils/types';
16
17
  import { createExamplePage } from './sdk-example';
17
18
  import { createOrMergeSvelteKitFiles, loadSvelteConfig } from './sdk-setup';
18
19
 
19
20
  import { setupCLIConfig } from './sentry-cli-setup';
20
21
 
21
- interface SvelteKitWizardOptions {
22
- promoCode?: string;
23
- }
24
-
25
22
  export async function runSvelteKitWizard(
26
- options: SvelteKitWizardOptions,
23
+ options: WizardOptions,
27
24
  ): Promise<void> {
28
25
  printWelcome({
29
26
  wizardName: 'Sentry SvelteKit Wizard',
@@ -35,7 +32,7 @@ export async function runSvelteKitWizard(
35
32
  const packageJson = await getPackageDotJson();
36
33
  await ensurePackageIsInstalled(packageJson, '@sveltejs/kit', 'Sveltekit');
37
34
 
38
- const { url: sentryUrl, selfHosted } = await askForSelfHosted();
35
+ const { url: sentryUrl, selfHosted } = await askForSelfHosted(options.url);
39
36
 
40
37
  const { projects, apiKeys } = await askForWizardLogin({
41
38
  promoCode: options.promoCode,
package/src/telemetry.ts CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  makeNodeTransport,
7
7
  NodeClient,
8
8
  runWithAsyncContext,
9
+ trace,
9
10
  } from '@sentry/node';
10
11
  import packageJson from '../package.json';
11
12
 
@@ -26,6 +27,7 @@ export async function withTelemetry<F>(
26
27
  const transaction = sentryHub.startTransaction({
27
28
  name: 'sentry-wizard-execution',
28
29
  status: 'ok',
30
+ op: 'wizard.flow',
29
31
  });
30
32
  sentryHub.getScope().setSpan(transaction);
31
33
  const sentrySession = sentryHub.startSession();
@@ -74,6 +76,8 @@ function createSentryInstance(enabled: boolean, integration: string) {
74
76
  },
75
77
 
76
78
  transport: makeNodeTransport,
79
+
80
+ debug: true,
77
81
  });
78
82
 
79
83
  const hub = new Hub(client);
@@ -84,3 +88,7 @@ function createSentryInstance(enabled: boolean, integration: string) {
84
88
 
85
89
  return { sentryHub: hub, sentryClient: client };
86
90
  }
91
+
92
+ export function traceStep<T>(step: string, callback: () => T): T {
93
+ return trace({ name: step, op: 'wizard.step' }, () => callback());
94
+ }
@@ -0,0 +1,44 @@
1
+ import * as child_process from 'child_process';
2
+ import * as https from 'https';
3
+ import * as fs from 'fs';
4
+
5
+ export function hasSentryCLI(): boolean {
6
+ try {
7
+ child_process.execSync('sentry-cli --version');
8
+ return true;
9
+ } catch (e) {
10
+ return false;
11
+ }
12
+ }
13
+
14
+ export async function installSentryCLI(): Promise<void> {
15
+ const httpAsync = new Promise((resolve, reject) => {
16
+ const file = fs.createWriteStream('installcli.sh');
17
+ https.get('https://sentry.io/get-cli/', (response) => {
18
+ response.pipe(file);
19
+ file.on('finish', () => {
20
+ file.close();
21
+ try {
22
+ child_process.execSync('bash ./installcli.sh');
23
+ } catch (e) {
24
+ reject(e);
25
+ return
26
+ }
27
+ fs.unlinkSync('installcli.sh');
28
+ resolve(null);
29
+ });
30
+
31
+ file.on('error', (err) => {
32
+ fs.unlinkSync('installcli.sh');
33
+ reject(err);
34
+ });
35
+ });
36
+ });
37
+
38
+ await httpAsync;
39
+ }
40
+
41
+ export function execute(command: string): string {
42
+ const output = child_process.execSync(command);
43
+ return output.toString();
44
+ }
@@ -10,6 +10,11 @@ import { URL } from 'url';
10
10
  import { promisify } from 'util';
11
11
  import * as Sentry from '@sentry/node';
12
12
  import { windowedSelect } from './vendor/clack-custom-select';
13
+ import { hasPackageInstalled, PackageDotJson } from './package-json';
14
+
15
+ const opn = require('opn') as (
16
+ url: string,
17
+ ) => Promise<childProcess.ChildProcess>;
13
18
 
14
19
  const SAAS_URL = 'https://sentry.io/';
15
20
 
@@ -20,16 +25,11 @@ interface WizardProjectData {
20
25
  projects: SentryProjectData[];
21
26
  }
22
27
 
23
- export type PackageDotJson = {
24
- scripts?: Record<string, string>;
25
- dependencies?: Record<string, string>;
26
- devDependencies?: Record<string, string>;
27
- };
28
-
29
28
  export interface SentryProjectData {
30
29
  id: string;
31
30
  slug: string;
32
31
  name: string;
32
+ platform: string;
33
33
  organization: {
34
34
  slug: string;
35
35
  };
@@ -126,10 +126,16 @@ export async function confirmContinueEvenThoughNoGitRepo(): Promise<void> {
126
126
  }
127
127
  }
128
128
 
129
+ export async function askToInstallSentryCLI(): Promise<boolean> {
130
+ return await abortIfCancelled(clack.confirm({
131
+ message: "You don't have Sentry CLI installed. Do you want to install it?"
132
+ }));
133
+ }
134
+
129
135
  export async function askForWizardLogin(options: {
130
136
  url: string;
131
137
  promoCode?: string;
132
- platform?: 'javascript-nextjs' | 'javascript-sveltekit';
138
+ platform?: 'javascript-nextjs' | 'javascript-sveltekit' | 'apple-ios';
133
139
  }): Promise<WizardProjectData> {
134
140
  Sentry.setTag('has-promo-code', !!options.promoCode);
135
141
 
@@ -179,14 +185,18 @@ export async function askForWizardLogin(options: {
179
185
  loginUrl.searchParams.set('code', options.promoCode);
180
186
  }
181
187
 
188
+ const urlToOpen = loginUrl.toString();
182
189
  clack.log.info(
183
190
  `${chalk.bold(
184
- `Please open the following link in your browser to ${
185
- hasSentryAccount ? 'log' : 'sign'
191
+ `If the browser window didn't open automatically, please open the following link to ${hasSentryAccount ? 'log' : 'sign'
186
192
  } into Sentry:`,
187
- )}\n\n${chalk.cyan(loginUrl.toString())}`,
193
+ )}\n\n${chalk.cyan(urlToOpen)}`,
188
194
  );
189
195
 
196
+ opn(urlToOpen).catch(() => {
197
+ // opn throws in environments that don't have a browser (e.g. remote shells) so we just noop here
198
+ });
199
+
190
200
  const loginSpinner = clack.spinner();
191
201
 
192
202
  loginSpinner.start(
@@ -225,6 +235,23 @@ export async function askForWizardLogin(options: {
225
235
  return data;
226
236
  }
227
237
 
238
+ export async function askForItemSelection(items: string[], message: string): Promise<{ value: string, index: number }> {
239
+ const selection: { value: string, index: number } | symbol = await abortIfCancelled(
240
+ windowedSelect({
241
+ maxItems: 12,
242
+ message: message,
243
+ options: items.map((item, index) => {
244
+ return {
245
+ value: { value: item, index: index },
246
+ label: item,
247
+ };
248
+ }),
249
+ }),
250
+ );
251
+
252
+ return selection;
253
+ }
254
+
228
255
  export async function askForProjectSelection(
229
256
  projects: SentryProjectData[],
230
257
  ): Promise<SentryProjectData> {
@@ -242,6 +269,7 @@ export async function askForProjectSelection(
242
269
  );
243
270
 
244
271
  Sentry.setTag('project', selection.slug);
272
+ Sentry.setTag('project-platform', selection.platform);
245
273
  Sentry.setUser({ id: selection.organization.slug });
246
274
 
247
275
  return selection;
@@ -250,11 +278,13 @@ export async function askForProjectSelection(
250
278
  export async function installPackage({
251
279
  packageName,
252
280
  alreadyInstalled,
281
+ askBeforeUpdating = true,
253
282
  }: {
254
283
  packageName: string;
255
284
  alreadyInstalled: boolean;
285
+ askBeforeUpdating?: boolean;
256
286
  }): Promise<void> {
257
- if (alreadyInstalled) {
287
+ if (alreadyInstalled && askBeforeUpdating) {
258
288
  const shouldUpdatePackage = await abortIfCancelled(
259
289
  clack.confirm({
260
290
  message: `The ${chalk.bold.cyan(
@@ -306,34 +336,54 @@ export async function installPackage({
306
336
  );
307
337
  }
308
338
 
309
- export async function askForSelfHosted(): Promise<{
339
+ /**
340
+ * Asks users if they are using SaaS or self-hosted Sentry and returns the validated URL.
341
+ *
342
+ * If users started the wizard with a --url arg, that URL is used as the default and we skip
343
+ * the self-hosted question. However, the passed url is still validated and in case it's
344
+ * invalid, users are asked to enter a new one until it is valid.
345
+ *
346
+ * @param urlFromArgs the url passed via the --url arg
347
+ */
348
+ export async function askForSelfHosted(urlFromArgs?: string): Promise<{
310
349
  url: string;
311
350
  selfHosted: boolean;
312
351
  }> {
313
- const choice: 'saas' | 'self-hosted' | symbol = await abortIfCancelled(
314
- clack.select({
315
- message: 'Are you using Sentry SaaS or self-hosted Sentry?',
316
- options: [
317
- { value: 'saas', label: 'Sentry SaaS (sentry.io)' },
318
- { value: 'self-hosted', label: 'Self-hosted/on-premise/single-tenant' },
319
- ],
320
- }),
321
- );
352
+ if (!urlFromArgs) {
353
+ const choice: 'saas' | 'self-hosted' | symbol = await abortIfCancelled(
354
+ clack.select({
355
+ message: 'Are you using Sentry SaaS or self-hosted Sentry?',
356
+ options: [
357
+ { value: 'saas', label: 'Sentry SaaS (sentry.io)' },
358
+ {
359
+ value: 'self-hosted',
360
+ label: 'Self-hosted/on-premise/single-tenant',
361
+ },
362
+ ],
363
+ }),
364
+ );
322
365
 
323
- if (choice === 'saas') {
324
- Sentry.setTag('url', SAAS_URL);
325
- Sentry.setTag('self-hosted', false);
326
- return { url: SAAS_URL, selfHosted: false };
366
+ if (choice === 'saas') {
367
+ Sentry.setTag('url', SAAS_URL);
368
+ Sentry.setTag('self-hosted', false);
369
+ return { url: SAAS_URL, selfHosted: false };
370
+ }
327
371
  }
328
372
 
329
373
  let validUrl: string | undefined;
374
+ let tmpUrlFromArgs = urlFromArgs;
375
+
330
376
  while (validUrl === undefined) {
331
- const url = await abortIfCancelled(
332
- clack.text({
333
- message: 'Please enter the URL of your self-hosted Sentry instance.',
334
- placeholder: 'https://sentry.io/',
335
- }),
336
- );
377
+ const url =
378
+ tmpUrlFromArgs ||
379
+ (await abortIfCancelled(
380
+ clack.text({
381
+ message: `Please enter the URL of your ${urlFromArgs ? '' : 'self-hosted '
382
+ }Sentry instance.`,
383
+ placeholder: 'https://sentry.io/',
384
+ }),
385
+ ));
386
+ tmpUrlFromArgs = undefined;
337
387
 
338
388
  try {
339
389
  validUrl = new URL(url).toString();
@@ -344,13 +394,16 @@ export async function askForSelfHosted(): Promise<{
344
394
  }
345
395
  } catch {
346
396
  clack.log.error(
347
- 'Please enter a valid URL. (It should look something like "http://sentry.mydomain.com/")',
397
+ 'Please enter a valid URL. (It should look something like "https://sentry.mydomain.com/")',
348
398
  );
349
399
  }
350
400
  }
351
401
 
402
+ const isSelfHostedUrl = new URL(validUrl).host !== new URL(SAAS_URL).host;
403
+
352
404
  Sentry.setTag('url', validUrl);
353
- Sentry.setTag('self-hosted', true);
405
+ Sentry.setTag('self-hosted', isSelfHostedUrl);
406
+
354
407
  return { url: validUrl, selfHosted: true };
355
408
  }
356
409
 
@@ -549,25 +602,8 @@ export async function getPackageDotJson(): Promise<PackageDotJson> {
549
602
  return packageJson || {};
550
603
  }
551
604
 
552
- export function hasPackageInstalled(
553
- packageName: string,
554
- packageJson: PackageDotJson,
555
- ): boolean {
556
- return (
557
- !!packageJson?.dependencies?.[packageName] ||
558
- !!packageJson?.devDependencies?.[packageName]
559
- );
560
- }
561
-
562
605
  async function getPackageManager(): Promise<string> {
563
- let detectedPackageManager;
564
- if (fs.existsSync(path.join(process.cwd(), 'yarn.lock'))) {
565
- detectedPackageManager = 'yarn';
566
- } else if (fs.existsSync(path.join(process.cwd(), 'package-lock.json'))) {
567
- detectedPackageManager = 'npm';
568
- } else if (fs.existsSync(path.join(process.cwd(), 'pnpm-lock.yaml'))) {
569
- detectedPackageManager = 'pnpm';
570
- }
606
+ const detectedPackageManager = detectPackageManager();
571
607
 
572
608
  if (detectedPackageManager) {
573
609
  return detectedPackageManager;
@@ -588,3 +624,16 @@ async function getPackageManager(): Promise<string> {
588
624
 
589
625
  return selectedPackageManager;
590
626
  }
627
+
628
+ export function detectPackageManager(): 'yarn' | 'npm' | 'pnpm' | undefined {
629
+ if (fs.existsSync(path.join(process.cwd(), 'yarn.lock'))) {
630
+ return 'yarn';
631
+ }
632
+ if (fs.existsSync(path.join(process.cwd(), 'package-lock.json'))) {
633
+ return 'npm';
634
+ }
635
+ if (fs.existsSync(path.join(process.cwd(), 'pnpm-lock.yaml'))) {
636
+ return 'pnpm';
637
+ }
638
+ return undefined;
639
+ }
@@ -0,0 +1,45 @@
1
+ export type PackageDotJson = {
2
+ scripts?: Record<string, string>;
3
+ dependencies?: Record<string, string>;
4
+ devDependencies?: Record<string, string>;
5
+ };
6
+
7
+ type NpmPackage = {
8
+ name: string;
9
+ version: string;
10
+ };
11
+
12
+ /**
13
+ * Checks if @param packageJson has any of the @param packageNamesList package names
14
+ * listed as a dependency or devDependency.
15
+ * If so, it returns the first package name that is found, including the
16
+ * version (range) specified in the package.json.
17
+ */
18
+ export function findInstalledPackageFromList(
19
+ packageNamesList: string[],
20
+ packageJson: PackageDotJson,
21
+ ): NpmPackage | undefined {
22
+ return packageNamesList
23
+ .map((packageName) => ({
24
+ name: packageName,
25
+ version: getPackageVersion(packageName, packageJson),
26
+ }))
27
+ .find((sdkPackage): sdkPackage is NpmPackage => !!sdkPackage.version);
28
+ }
29
+
30
+ export function hasPackageInstalled(
31
+ packageName: string,
32
+ packageJson: PackageDotJson,
33
+ ): boolean {
34
+ return getPackageVersion(packageName, packageJson) !== undefined;
35
+ }
36
+
37
+ export function getPackageVersion(
38
+ packageName: string,
39
+ packageJson: PackageDotJson,
40
+ ): string | undefined {
41
+ return (
42
+ packageJson?.dependencies?.[packageName] ||
43
+ packageJson?.devDependencies?.[packageName]
44
+ );
45
+ }
@@ -0,0 +1,18 @@
1
+ export type WizardOptions = {
2
+ /**
3
+ * Controls whether the wizard should send telemetry data to Sentry.
4
+ */
5
+ telemetryEnabled: boolean;
6
+
7
+ /**
8
+ * The promo code to use while signing up for Sentry.
9
+ * This can be passed via the --promo-code arg.
10
+ */
11
+ promoCode?: string;
12
+
13
+ /**
14
+ * The url of the Sentry instance to use.
15
+ * This can be passed via the `-u` or `--url` arg.
16
+ */
17
+ url?: string;
18
+ };
@@ -1 +0,0 @@
1
- {"version":3,"file":"NextJs.js","sourceRoot":"","sources":["../../../../lib/Steps/Integrations/NextJs.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,mEAAoE;AAGpE,qDAAoD;AAEpD;;;GAGG;AACH;IAA4B,0BAAe;IACzC,gBAA6B,KAAW;QAAxC,YACE,kBAAM,KAAK,CAAC,SACb;QAF4B,WAAK,GAAL,KAAK,CAAM;;IAExC,CAAC;IAEY,qBAAI,GAAjB,UAAkB,QAAiB;;;;4BACjC,qBAAM,IAAA,+BAAe,EAAC,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC,EAAA;;wBAA1D,SAA0D,CAAC;wBAC3D,sBAAO,EAAE,EAAC;;;;KACX;IAEY,gCAAe,GAA5B,UAA6B,QAAiB;;;gBAC5C,kEAAkE;gBAClE,IAAI,IAAI,CAAC,gBAAgB,EAAE;oBACzB,sBAAO,IAAI,CAAC,gBAAgB,EAAC;iBAC9B;gBACD,6DAA6D;gBAC7D,sBAAO,IAAI,CAAC,eAAe,EAAC;;;KAC7B;IACH,aAAC;AAAD,CAAC,AAlBD,CAA4B,iCAAe,GAkB1C;AAlBY,wBAAM","sourcesContent":["import type { Answers } from 'inquirer';\nimport { runNextjsWizard } from '../../../src/nextjs/nextjs-wizard';\n\nimport type { Args } from '../../Constants';\nimport { BaseIntegration } from './BaseIntegration';\n\n/**\n * This class just redirects to the new `nextjs-wizard.ts` flow\n * for anyone calling the wizard without the '-i nextjs' flag.\n */\nexport class NextJs extends BaseIntegration {\n public constructor(protected _argv: Args) {\n super(_argv);\n }\n\n public async emit(_answers: Answers): Promise<Answers> {\n await runNextjsWizard({ promoCode: this._argv.promoCode });\n return {};\n }\n\n public async shouldConfigure(_answers: Answers): Promise<Answers> {\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n if (this._shouldConfigure) {\n return this._shouldConfigure;\n }\n // eslint-disable-next-line @typescript-eslint/unbound-method\n return this.shouldConfigure;\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"SvelteKit.js","sourceRoot":"","sources":["../../../../lib/Steps/Integrations/SvelteKit.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,4EAA6E;AAG7E,qDAAoD;AAEpD;;;GAGG;AACH;IAA+B,6BAAe;IAC5C,mBAA6B,KAAW;QAAxC,YACE,kBAAM,KAAK,CAAC,SACb;QAF4B,WAAK,GAAL,KAAK,CAAM;;IAExC,CAAC;IAEY,wBAAI,GAAjB,UAAkB,QAAiB;;;;4BACjC,qBAAM,IAAA,qCAAkB,EAAC,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC,EAAA;;wBAA7D,SAA6D,CAAC;wBAC9D,sBAAO,EAAE,EAAC;;;;KACX;IAEY,mCAAe,GAA5B,UAA6B,QAAiB;;;gBAC5C,kEAAkE;gBAClE,IAAI,IAAI,CAAC,gBAAgB,EAAE;oBACzB,sBAAO,IAAI,CAAC,gBAAgB,EAAC;iBAC9B;gBACD,6DAA6D;gBAC7D,sBAAO,IAAI,CAAC,eAAe,EAAC;;;KAC7B;IACH,gBAAC;AAAD,CAAC,AAlBD,CAA+B,iCAAe,GAkB7C;AAlBY,8BAAS","sourcesContent":["import type { Answers } from 'inquirer';\nimport { runSvelteKitWizard } from '../../../src/sveltekit/sveltekit-wizard';\n\nimport type { Args } from '../../Constants';\nimport { BaseIntegration } from './BaseIntegration';\n\n/**\n * This class just redirects to the new `sveltekit-wizard.ts` flow\n * for anyone calling the wizard without the '-i sveltekit' flag.\n */\nexport class SvelteKit extends BaseIntegration {\n public constructor(protected _argv: Args) {\n super(_argv);\n }\n\n public async emit(_answers: Answers): Promise<Answers> {\n await runSvelteKitWizard({ promoCode: this._argv.promoCode });\n return {};\n }\n\n public async shouldConfigure(_answers: Answers): Promise<Answers> {\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n if (this._shouldConfigure) {\n return this._shouldConfigure;\n }\n // eslint-disable-next-line @typescript-eslint/unbound-method\n return this.shouldConfigure;\n }\n}\n"]}