@sentry/wizard 3.9.2 → 3.11.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 +58 -6
- package/dist/lib/Constants.d.ts +2 -0
- package/dist/lib/Constants.js +10 -0
- package/dist/lib/Constants.js.map +1 -1
- package/dist/lib/Steps/ChooseIntegration.js +15 -4
- package/dist/lib/Steps/ChooseIntegration.js.map +1 -1
- package/dist/lib/Steps/Integrations/Android.d.ts +9 -0
- package/dist/lib/Steps/Integrations/Android.js +86 -0
- package/dist/lib/Steps/Integrations/Android.js.map +1 -0
- package/dist/lib/Steps/Integrations/Cordova.js +5 -1
- package/dist/lib/Steps/Integrations/Cordova.js.map +1 -1
- package/dist/lib/Steps/Integrations/ReactNative.js +3 -3
- package/dist/lib/Steps/Integrations/ReactNative.js.map +1 -1
- package/dist/lib/Steps/Integrations/Remix.d.ts +12 -0
- package/dist/lib/Steps/Integrations/Remix.js +98 -0
- package/dist/lib/Steps/Integrations/Remix.js.map +1 -0
- package/dist/lib/Steps/PromptForParameters.js +36 -3
- package/dist/lib/Steps/PromptForParameters.js.map +1 -1
- package/dist/lib/Steps/SentryProjectSelector.js +1 -1
- package/dist/lib/Steps/SentryProjectSelector.js.map +1 -1
- package/dist/package.json +4 -3
- package/dist/src/android/android-wizard.d.ts +2 -0
- package/dist/src/android/android-wizard.js +217 -0
- package/dist/src/android/android-wizard.js.map +1 -0
- package/dist/src/android/code-tools.d.ts +39 -0
- package/dist/src/android/code-tools.js +161 -0
- package/dist/src/android/code-tools.js.map +1 -0
- package/dist/src/android/gradle.d.ts +62 -0
- package/dist/src/android/gradle.js +281 -0
- package/dist/src/android/gradle.js.map +1 -0
- package/dist/src/android/manifest.d.ts +57 -0
- package/dist/src/android/manifest.js +183 -0
- package/dist/src/android/manifest.js.map +1 -0
- package/dist/src/android/templates.d.ts +11 -0
- package/dist/src/android/templates.js +34 -0
- package/dist/src/android/templates.js.map +1 -0
- package/dist/src/apple/apple-wizard.js +123 -64
- package/dist/src/apple/apple-wizard.js.map +1 -1
- package/dist/src/apple/cocoapod.js +4 -3
- package/dist/src/apple/cocoapod.js.map +1 -1
- package/dist/src/apple/code-tools.d.ts +1 -1
- package/dist/src/apple/code-tools.js +43 -19
- package/dist/src/apple/code-tools.js.map +1 -1
- package/dist/src/apple/fastlane.d.ts +1 -1
- package/dist/src/apple/fastlane.js +12 -6
- package/dist/src/apple/fastlane.js.map +1 -1
- package/dist/src/apple/templates.d.ts +2 -2
- package/dist/src/apple/templates.js +4 -4
- package/dist/src/apple/templates.js.map +1 -1
- package/dist/src/apple/xcode-manager.d.ts +19 -3
- package/dist/src/apple/xcode-manager.js +126 -24
- package/dist/src/apple/xcode-manager.js.map +1 -1
- package/dist/src/nextjs/nextjs-wizard.js +49 -11
- package/dist/src/nextjs/nextjs-wizard.js.map +1 -1
- package/dist/src/nextjs/templates.d.ts +2 -0
- package/dist/src/nextjs/templates.js +6 -2
- package/dist/src/nextjs/templates.js.map +1 -1
- package/dist/src/remix/codemods/handle-error.d.ts +2 -0
- package/dist/src/remix/codemods/handle-error.js +70 -0
- package/dist/src/remix/codemods/handle-error.js.map +1 -0
- package/dist/src/remix/codemods/root-v1.d.ts +1 -0
- package/dist/src/remix/codemods/root-v1.js +133 -0
- package/dist/src/remix/codemods/root-v1.js.map +1 -0
- package/dist/src/remix/codemods/root-v2.d.ts +1 -0
- package/dist/src/remix/codemods/root-v2.js +134 -0
- package/dist/src/remix/codemods/root-v2.js.map +1 -0
- package/dist/src/remix/remix-wizard.d.ts +2 -0
- package/dist/src/remix/remix-wizard.js +196 -0
- package/dist/src/remix/remix-wizard.js.map +1 -0
- package/dist/src/remix/sdk-setup.d.ts +18 -0
- package/dist/src/remix/sdk-setup.js +293 -0
- package/dist/src/remix/sdk-setup.js.map +1 -0
- package/dist/src/remix/templates.d.ts +2 -0
- package/dist/src/remix/templates.js +6 -0
- package/dist/src/remix/templates.js.map +1 -0
- package/dist/src/remix/utils.d.ts +6 -0
- package/dist/src/remix/utils.js +55 -0
- package/dist/src/remix/utils.js.map +1 -0
- package/dist/src/sourcemaps/sourcemaps-wizard.js +49 -25
- package/dist/src/sourcemaps/sourcemaps-wizard.js.map +1 -1
- package/dist/src/sourcemaps/tools/nextjs.js +1 -1
- package/dist/src/sourcemaps/tools/nextjs.js.map +1 -1
- package/dist/src/sourcemaps/tools/remix.d.ts +3 -0
- package/dist/src/sourcemaps/tools/remix.js +125 -0
- package/dist/src/sourcemaps/tools/remix.js.map +1 -0
- package/dist/src/sourcemaps/tools/sentry-cli.js +19 -16
- package/dist/src/sourcemaps/tools/sentry-cli.js.map +1 -1
- package/dist/src/sourcemaps/tools/vite.d.ts +2 -1
- package/dist/src/sourcemaps/tools/vite.js +99 -12
- package/dist/src/sourcemaps/tools/vite.js.map +1 -1
- package/dist/src/sourcemaps/utils/detect-tool.d.ts +1 -1
- package/dist/src/sourcemaps/utils/detect-tool.js +1 -0
- package/dist/src/sourcemaps/utils/detect-tool.js.map +1 -1
- package/dist/src/sveltekit/sdk-setup.js +3 -3
- package/dist/src/sveltekit/sdk-setup.js.map +1 -1
- package/dist/src/sveltekit/sveltekit-wizard.js +34 -44
- package/dist/src/sveltekit/sveltekit-wizard.js.map +1 -1
- package/dist/src/telemetry.js +1 -0
- package/dist/src/telemetry.js.map +1 -1
- package/dist/src/utils/ast-utils.d.ts +2 -2
- package/dist/src/utils/ast-utils.js +7 -7
- package/dist/src/utils/ast-utils.js.map +1 -1
- package/dist/src/utils/clack-utils.d.ts +23 -28
- package/dist/src/utils/clack-utils.js +287 -244
- package/dist/src/utils/clack-utils.js.map +1 -1
- package/dist/src/utils/package-manager.d.ts +10 -0
- package/dist/{lib/Helper/PackageManager.js → src/utils/package-manager.js} +42 -74
- package/dist/src/utils/package-manager.js.map +1 -0
- package/dist/src/utils/release-registry.d.ts +1 -0
- package/dist/src/utils/release-registry.js +68 -0
- package/dist/src/utils/release-registry.js.map +1 -0
- package/dist/src/utils/sentrycli-utils.d.ts +4 -0
- package/dist/src/utils/sentrycli-utils.js +41 -0
- package/dist/src/utils/sentrycli-utils.js.map +1 -0
- package/dist/test/sourcemaps/tools/vite.test.d.ts +1 -0
- package/dist/test/sourcemaps/tools/vite.test.js +132 -0
- package/dist/test/sourcemaps/tools/vite.test.js.map +1 -0
- package/lib/Constants.ts +10 -0
- package/lib/Steps/ChooseIntegration.ts +14 -3
- package/lib/Steps/Integrations/Android.ts +23 -0
- package/lib/Steps/Integrations/Cordova.ts +5 -1
- package/lib/Steps/Integrations/ReactNative.ts +9 -3
- package/lib/Steps/Integrations/Remix.ts +32 -0
- package/lib/Steps/PromptForParameters.ts +48 -3
- package/lib/Steps/SentryProjectSelector.ts +3 -1
- package/package.json +4 -3
- package/src/android/android-wizard.ts +196 -0
- package/src/android/code-tools.ts +156 -0
- package/src/android/gradle.ts +245 -0
- package/src/android/manifest.ts +180 -0
- package/src/android/templates.ts +88 -0
- package/src/apple/apple-wizard.ts +113 -35
- package/src/apple/cocoapod.ts +6 -3
- package/src/apple/code-tools.ts +46 -18
- package/src/apple/fastlane.ts +6 -12
- package/src/apple/templates.ts +2 -8
- package/src/apple/xcode-manager.ts +167 -25
- package/src/nextjs/nextjs-wizard.ts +72 -8
- package/src/nextjs/templates.ts +16 -2
- package/src/remix/codemods/handle-error.ts +67 -0
- package/src/remix/codemods/root-v1.ts +91 -0
- package/src/remix/codemods/root-v2.ts +84 -0
- package/src/remix/remix-wizard.ts +132 -0
- package/src/remix/sdk-setup.ts +300 -0
- package/src/remix/templates.ts +15 -0
- package/src/remix/utils.ts +41 -0
- package/src/sourcemaps/sourcemaps-wizard.ts +28 -5
- package/src/sourcemaps/tools/nextjs.ts +2 -2
- package/src/sourcemaps/tools/remix.ts +90 -0
- package/src/sourcemaps/tools/sentry-cli.ts +8 -7
- package/src/sourcemaps/tools/vite.ts +136 -6
- package/src/sourcemaps/utils/detect-tool.ts +4 -1
- package/src/sveltekit/sdk-setup.ts +4 -4
- package/src/sveltekit/sveltekit-wizard.ts +5 -14
- package/src/telemetry.ts +2 -0
- package/src/utils/ast-utils.ts +7 -5
- package/src/utils/clack-utils.ts +366 -258
- package/src/utils/package-manager.ts +61 -0
- package/src/utils/release-registry.ts +19 -0
- package/src/utils/sentrycli-utils.ts +22 -0
- package/test/sourcemaps/tools/vite.test.ts +149 -0
- package/dist/lib/Helper/PackageManager.d.ts +0 -22
- package/dist/lib/Helper/PackageManager.js.map +0 -1
- package/dist/src/utils/vendor/clack-custom-select.d.ts +0 -21
- package/dist/src/utils/vendor/clack-custom-select.js +0 -137
- package/dist/src/utils/vendor/clack-custom-select.js.map +0 -1
- package/lib/Helper/PackageManager.ts +0 -59
- package/src/utils/vendor/clack-custom-select.ts +0 -160
package/src/utils/clack-utils.ts
CHANGED
|
@@ -7,12 +7,16 @@ import * as fs from 'fs';
|
|
|
7
7
|
import * as path from 'path';
|
|
8
8
|
import { setInterval } from 'timers';
|
|
9
9
|
import { URL } from 'url';
|
|
10
|
-
import { promisify } from 'util';
|
|
11
10
|
import * as Sentry from '@sentry/node';
|
|
12
|
-
import { windowedSelect } from './vendor/clack-custom-select';
|
|
13
11
|
import { hasPackageInstalled, PackageDotJson } from './package-json';
|
|
14
12
|
import { SentryProjectData, WizardOptions } from './types';
|
|
15
13
|
import { traceStep } from '../telemetry';
|
|
14
|
+
import {
|
|
15
|
+
detectPackageManger,
|
|
16
|
+
PackageManager,
|
|
17
|
+
installPackageWithPackageManager,
|
|
18
|
+
packageManagers,
|
|
19
|
+
} from './package-manager';
|
|
16
20
|
|
|
17
21
|
const opn = require('opn') as (
|
|
18
22
|
url: string,
|
|
@@ -20,6 +24,7 @@ const opn = require('opn') as (
|
|
|
20
24
|
|
|
21
25
|
export const SENTRY_DOT_ENV_FILE = '.env.sentry-build-plugin';
|
|
22
26
|
export const SENTRY_CLI_RC_FILE = '.sentryclirc';
|
|
27
|
+
export const SENTRY_PROPERTIES_FILE = 'sentry.properties';
|
|
23
28
|
|
|
24
29
|
const SAAS_URL = 'https://sentry.io/';
|
|
25
30
|
|
|
@@ -30,6 +35,38 @@ interface WizardProjectData {
|
|
|
30
35
|
projects: SentryProjectData[];
|
|
31
36
|
}
|
|
32
37
|
|
|
38
|
+
export interface CliSetupConfig {
|
|
39
|
+
filename: string;
|
|
40
|
+
name: string;
|
|
41
|
+
|
|
42
|
+
likelyAlreadyHasAuthToken(contents: string): boolean;
|
|
43
|
+
tokenContent(authToken: string): string;
|
|
44
|
+
|
|
45
|
+
likelyAlreadyHasOrgAndProject(contents: string): boolean;
|
|
46
|
+
orgAndProjContent(org: string, project: string): string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const sourceMapsCliSetupConfig: CliSetupConfig = {
|
|
50
|
+
filename: SENTRY_CLI_RC_FILE,
|
|
51
|
+
name: 'source maps',
|
|
52
|
+
likelyAlreadyHasAuthToken: function (contents: string): boolean {
|
|
53
|
+
return !!(contents.includes('[auth]') && contents.match(/token=./g));
|
|
54
|
+
},
|
|
55
|
+
tokenContent: function (authToken: string): string {
|
|
56
|
+
return `[auth]\ntoken=${authToken}`;
|
|
57
|
+
},
|
|
58
|
+
likelyAlreadyHasOrgAndProject: function (contents: string): boolean {
|
|
59
|
+
return !!(
|
|
60
|
+
contents.includes('[defaults]') &&
|
|
61
|
+
contents.match(/org=./g) &&
|
|
62
|
+
contents.match(/project=./g)
|
|
63
|
+
);
|
|
64
|
+
},
|
|
65
|
+
orgAndProjContent: function (org: string, project: string): string {
|
|
66
|
+
return `[defaults]\norg=${org}\nproject=${project}`;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
33
70
|
export async function abort(message?: string, status?: number): Promise<never> {
|
|
34
71
|
clack.outro(message ?? 'Wizard setup cancelled.');
|
|
35
72
|
const sentryHub = Sentry.getCurrentHub();
|
|
@@ -66,6 +103,7 @@ export function printWelcome(options: {
|
|
|
66
103
|
wizardName: string;
|
|
67
104
|
promoCode?: string;
|
|
68
105
|
message?: string;
|
|
106
|
+
telemetryEnabled?: boolean;
|
|
69
107
|
}): void {
|
|
70
108
|
let wizardPackage: { version?: string } = {};
|
|
71
109
|
|
|
@@ -96,6 +134,10 @@ export function printWelcome(options: {
|
|
|
96
134
|
welcomeText += `\n\nVersion: ${wizardPackage.version}`;
|
|
97
135
|
}
|
|
98
136
|
|
|
137
|
+
if (options.telemetryEnabled) {
|
|
138
|
+
welcomeText += `\n\nYou are using the Sentry Wizard with telemetry enabled. This helps us improve the Wizard.\nYou can disable it at any time by running \`sentry-wizard --disable-telemetry\`.`;
|
|
139
|
+
}
|
|
140
|
+
|
|
99
141
|
clack.note(welcomeText);
|
|
100
142
|
}
|
|
101
143
|
|
|
@@ -129,115 +171,13 @@ export async function askToInstallSentryCLI(): Promise<boolean> {
|
|
|
129
171
|
);
|
|
130
172
|
}
|
|
131
173
|
|
|
132
|
-
export async function askForWizardLogin(options: {
|
|
133
|
-
url: string;
|
|
134
|
-
promoCode?: string;
|
|
135
|
-
platform?: 'javascript-nextjs' | 'javascript-sveltekit' | 'apple-ios';
|
|
136
|
-
}): Promise<WizardProjectData> {
|
|
137
|
-
Sentry.setTag('has-promo-code', !!options.promoCode);
|
|
138
|
-
|
|
139
|
-
let hasSentryAccount = await clack.confirm({
|
|
140
|
-
message: 'Do you already have a Sentry account?',
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
hasSentryAccount = await abortIfCancelled(hasSentryAccount);
|
|
144
|
-
|
|
145
|
-
Sentry.setTag('already-has-sentry-account', hasSentryAccount);
|
|
146
|
-
|
|
147
|
-
let wizardHash: string;
|
|
148
|
-
try {
|
|
149
|
-
wizardHash = (
|
|
150
|
-
await axios.get<{ hash: string }>(`${options.url}api/0/wizard/`)
|
|
151
|
-
).data.hash;
|
|
152
|
-
} catch {
|
|
153
|
-
if (options.url !== SAAS_URL) {
|
|
154
|
-
clack.log.error('Loading Wizard failed. Did you provide the right URL?');
|
|
155
|
-
await abort(
|
|
156
|
-
chalk.red(
|
|
157
|
-
'Please check your configuration and try again.\n\n Let us know if you think this is an issue with the wizard or Sentry: https://github.com/getsentry/sentry-wizard/issues',
|
|
158
|
-
),
|
|
159
|
-
);
|
|
160
|
-
} else {
|
|
161
|
-
clack.log.error('Loading Wizard failed.');
|
|
162
|
-
await abort(
|
|
163
|
-
chalk.red(
|
|
164
|
-
'Please try again in a few minutes and let us know if this issue persists: https://github.com/getsentry/sentry-wizard/issues',
|
|
165
|
-
),
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const loginUrl = new URL(
|
|
171
|
-
`${options.url}account/settings/wizard/${wizardHash!}/`,
|
|
172
|
-
);
|
|
173
|
-
|
|
174
|
-
if (!hasSentryAccount) {
|
|
175
|
-
loginUrl.searchParams.set('signup', '1');
|
|
176
|
-
if (options.platform) {
|
|
177
|
-
loginUrl.searchParams.set('project_platform', options.platform);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (options.promoCode) {
|
|
182
|
-
loginUrl.searchParams.set('code', options.promoCode);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const urlToOpen = loginUrl.toString();
|
|
186
|
-
clack.log.info(
|
|
187
|
-
`${chalk.bold(
|
|
188
|
-
`If the browser window didn't open automatically, please open the following link to ${
|
|
189
|
-
hasSentryAccount ? 'log' : 'sign'
|
|
190
|
-
} into Sentry:`,
|
|
191
|
-
)}\n\n${chalk.cyan(urlToOpen)}`,
|
|
192
|
-
);
|
|
193
|
-
|
|
194
|
-
opn(urlToOpen).catch(() => {
|
|
195
|
-
// opn throws in environments that don't have a browser (e.g. remote shells) so we just noop here
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
const loginSpinner = clack.spinner();
|
|
199
|
-
|
|
200
|
-
loginSpinner.start('Waiting for you to log in using the link above');
|
|
201
|
-
|
|
202
|
-
const data = await new Promise<WizardProjectData>((resolve) => {
|
|
203
|
-
const pollingInterval = setInterval(() => {
|
|
204
|
-
axios
|
|
205
|
-
.get<WizardProjectData>(`${options.url}api/0/wizard/${wizardHash}/`)
|
|
206
|
-
.then((result) => {
|
|
207
|
-
resolve(result.data);
|
|
208
|
-
clearTimeout(timeout);
|
|
209
|
-
clearInterval(pollingInterval);
|
|
210
|
-
void axios.delete(`${options.url}api/0/wizard/${wizardHash}/`);
|
|
211
|
-
})
|
|
212
|
-
.catch(() => {
|
|
213
|
-
// noop - just try again
|
|
214
|
-
});
|
|
215
|
-
}, 500);
|
|
216
|
-
|
|
217
|
-
const timeout = setTimeout(() => {
|
|
218
|
-
clearInterval(pollingInterval);
|
|
219
|
-
loginSpinner.stop(
|
|
220
|
-
'Login timed out. No worries - it happens to the best of us.',
|
|
221
|
-
);
|
|
222
|
-
|
|
223
|
-
Sentry.setTag('opened-wizard-link', false);
|
|
224
|
-
void abort('Please restart the Wizard and log in to complete the setup.');
|
|
225
|
-
}, 180_000);
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
loginSpinner.stop('Login complete.');
|
|
229
|
-
Sentry.setTag('opened-wizard-link', true);
|
|
230
|
-
|
|
231
|
-
return data;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
174
|
export async function askForItemSelection(
|
|
235
175
|
items: string[],
|
|
236
176
|
message: string,
|
|
237
177
|
): Promise<{ value: string; index: number }> {
|
|
238
178
|
const selection: { value: string; index: number } | symbol =
|
|
239
179
|
await abortIfCancelled(
|
|
240
|
-
|
|
180
|
+
clack.select({
|
|
241
181
|
maxItems: 12,
|
|
242
182
|
message: message,
|
|
243
183
|
options: items.map((item, index) => {
|
|
@@ -252,29 +192,6 @@ export async function askForItemSelection(
|
|
|
252
192
|
return selection;
|
|
253
193
|
}
|
|
254
194
|
|
|
255
|
-
export async function askForProjectSelection(
|
|
256
|
-
projects: SentryProjectData[],
|
|
257
|
-
): Promise<SentryProjectData> {
|
|
258
|
-
const selection: SentryProjectData | symbol = await abortIfCancelled(
|
|
259
|
-
windowedSelect({
|
|
260
|
-
maxItems: 12,
|
|
261
|
-
message: 'Select your Sentry project.',
|
|
262
|
-
options: projects.map((project) => {
|
|
263
|
-
return {
|
|
264
|
-
value: project,
|
|
265
|
-
label: `${project.organization.slug}/${project.slug}`,
|
|
266
|
-
};
|
|
267
|
-
}),
|
|
268
|
-
}),
|
|
269
|
-
);
|
|
270
|
-
|
|
271
|
-
Sentry.setTag('project', selection.slug);
|
|
272
|
-
Sentry.setTag('project-platform', selection.platform);
|
|
273
|
-
Sentry.setUser({ id: selection.organization.slug });
|
|
274
|
-
|
|
275
|
-
return selection;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
195
|
export async function installPackage({
|
|
279
196
|
packageName,
|
|
280
197
|
alreadyInstalled,
|
|
@@ -305,17 +222,11 @@ export async function installPackage({
|
|
|
305
222
|
sdkInstallSpinner.start(
|
|
306
223
|
`${alreadyInstalled ? 'Updating' : 'Installing'} ${chalk.bold.cyan(
|
|
307
224
|
packageName,
|
|
308
|
-
)} with ${chalk.bold(packageManager)}.`,
|
|
225
|
+
)} with ${chalk.bold(packageManager.label)}.`,
|
|
309
226
|
);
|
|
310
227
|
|
|
311
228
|
try {
|
|
312
|
-
|
|
313
|
-
await promisify(childProcess.exec)(`yarn add ${packageName}@latest`);
|
|
314
|
-
} else if (packageManager === 'pnpm') {
|
|
315
|
-
await promisify(childProcess.exec)(`pnpm add ${packageName}@latest`);
|
|
316
|
-
} else if (packageManager === 'npm') {
|
|
317
|
-
await promisify(childProcess.exec)(`npm install ${packageName}@latest`);
|
|
318
|
-
}
|
|
229
|
+
await installPackageWithPackageManager(packageManager, packageName);
|
|
319
230
|
} catch (e) {
|
|
320
231
|
sdkInstallSpinner.stop('Installation failed.');
|
|
321
232
|
clack.log.error(
|
|
@@ -332,144 +243,119 @@ export async function installPackage({
|
|
|
332
243
|
sdkInstallSpinner.stop(
|
|
333
244
|
`${alreadyInstalled ? 'Updated' : 'Installed'} ${chalk.bold.cyan(
|
|
334
245
|
packageName,
|
|
335
|
-
)} with ${chalk.bold(packageManager)}.`,
|
|
246
|
+
)} with ${chalk.bold(packageManager.label)}.`,
|
|
336
247
|
);
|
|
337
248
|
}
|
|
338
249
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
export async function askForSelfHosted(urlFromArgs?: string): Promise<{
|
|
349
|
-
url: string;
|
|
350
|
-
selfHosted: boolean;
|
|
351
|
-
}> {
|
|
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
|
-
);
|
|
365
|
-
|
|
366
|
-
if (choice === 'saas') {
|
|
367
|
-
Sentry.setTag('url', SAAS_URL);
|
|
368
|
-
Sentry.setTag('self-hosted', false);
|
|
369
|
-
return { url: SAAS_URL, selfHosted: false };
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
let validUrl: string | undefined;
|
|
374
|
-
let tmpUrlFromArgs = urlFromArgs;
|
|
375
|
-
|
|
376
|
-
while (validUrl === undefined) {
|
|
377
|
-
const url =
|
|
378
|
-
tmpUrlFromArgs ||
|
|
379
|
-
(await abortIfCancelled(
|
|
380
|
-
clack.text({
|
|
381
|
-
message: `Please enter the URL of your ${
|
|
382
|
-
urlFromArgs ? '' : 'self-hosted '
|
|
383
|
-
}Sentry instance.`,
|
|
384
|
-
placeholder: 'https://sentry.io/',
|
|
385
|
-
}),
|
|
386
|
-
));
|
|
387
|
-
tmpUrlFromArgs = undefined;
|
|
250
|
+
async function addOrgAndProjectToSentryCliRc(
|
|
251
|
+
org: string,
|
|
252
|
+
project: string,
|
|
253
|
+
setupConfig: CliSetupConfig,
|
|
254
|
+
): Promise<void> {
|
|
255
|
+
const configContents = fs.readFileSync(
|
|
256
|
+
path.join(process.cwd(), setupConfig.filename),
|
|
257
|
+
'utf8',
|
|
258
|
+
);
|
|
388
259
|
|
|
260
|
+
if (setupConfig.likelyAlreadyHasOrgAndProject(configContents)) {
|
|
261
|
+
clack.log.warn(
|
|
262
|
+
`${chalk.bold(
|
|
263
|
+
setupConfig.filename,
|
|
264
|
+
)} already has org and project. Will not add them.`,
|
|
265
|
+
);
|
|
266
|
+
} else {
|
|
389
267
|
try {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
268
|
+
await fs.promises.appendFile(
|
|
269
|
+
path.join(process.cwd(), setupConfig.filename),
|
|
270
|
+
`\n${setupConfig.orgAndProjContent(org, project)}\n`,
|
|
271
|
+
);
|
|
272
|
+
} catch (e) {
|
|
273
|
+
clack.log.warn(
|
|
274
|
+
`${chalk.bold(
|
|
275
|
+
setupConfig.filename,
|
|
276
|
+
)} could not be updated with org and project.`,
|
|
399
277
|
);
|
|
400
278
|
}
|
|
401
279
|
}
|
|
402
|
-
|
|
403
|
-
const isSelfHostedUrl = new URL(validUrl).host !== new URL(SAAS_URL).host;
|
|
404
|
-
|
|
405
|
-
Sentry.setTag('url', validUrl);
|
|
406
|
-
Sentry.setTag('self-hosted', isSelfHostedUrl);
|
|
407
|
-
|
|
408
|
-
return { url: validUrl, selfHosted: true };
|
|
409
280
|
}
|
|
410
281
|
|
|
411
|
-
export async function
|
|
412
|
-
|
|
413
|
-
|
|
282
|
+
export async function addSentryCliConfig(
|
|
283
|
+
authToken: string,
|
|
284
|
+
setupConfig: CliSetupConfig = sourceMapsCliSetupConfig,
|
|
285
|
+
orgSlug?: string,
|
|
286
|
+
projectSlug?: string,
|
|
287
|
+
): Promise<void> {
|
|
288
|
+
const configExists = fs.existsSync(
|
|
289
|
+
path.join(process.cwd(), setupConfig.filename),
|
|
414
290
|
);
|
|
415
|
-
if (
|
|
416
|
-
const
|
|
417
|
-
path.join(process.cwd(),
|
|
291
|
+
if (configExists) {
|
|
292
|
+
const configContents = fs.readFileSync(
|
|
293
|
+
path.join(process.cwd(), setupConfig.filename),
|
|
418
294
|
'utf8',
|
|
419
295
|
);
|
|
420
296
|
|
|
421
|
-
|
|
422
|
-
clircContents.includes('[auth]') && clircContents.match(/token=./g)
|
|
423
|
-
);
|
|
424
|
-
|
|
425
|
-
if (likelyAlreadyHasAuthToken) {
|
|
297
|
+
if (setupConfig.likelyAlreadyHasAuthToken(configContents)) {
|
|
426
298
|
clack.log.warn(
|
|
427
299
|
`${chalk.bold(
|
|
428
|
-
|
|
300
|
+
setupConfig.filename,
|
|
429
301
|
)} already has auth token. Will not add one.`,
|
|
430
302
|
);
|
|
431
303
|
} else {
|
|
432
304
|
try {
|
|
433
305
|
await fs.promises.writeFile(
|
|
434
|
-
path.join(process.cwd(),
|
|
435
|
-
`${
|
|
306
|
+
path.join(process.cwd(), setupConfig.filename),
|
|
307
|
+
`${configContents}\n${setupConfig.tokenContent(authToken)}\n`,
|
|
436
308
|
{ encoding: 'utf8', flag: 'w' },
|
|
437
309
|
);
|
|
438
310
|
clack.log.success(
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
311
|
+
chalk.greenBright(
|
|
312
|
+
`Added auth token to ${chalk.bold(
|
|
313
|
+
setupConfig.filename,
|
|
314
|
+
)} for you to test uploading ${setupConfig.name} locally.`,
|
|
315
|
+
),
|
|
442
316
|
);
|
|
443
317
|
} catch {
|
|
444
318
|
clack.log.warning(
|
|
445
319
|
`Failed to add auth token to ${chalk.bold(
|
|
446
|
-
|
|
447
|
-
)}. Uploading
|
|
320
|
+
setupConfig.filename,
|
|
321
|
+
)}. Uploading ${
|
|
322
|
+
setupConfig.name
|
|
323
|
+
} during build will likely not work locally.`,
|
|
448
324
|
);
|
|
449
325
|
}
|
|
450
326
|
}
|
|
451
327
|
} else {
|
|
452
328
|
try {
|
|
453
329
|
await fs.promises.writeFile(
|
|
454
|
-
path.join(process.cwd(),
|
|
455
|
-
|
|
330
|
+
path.join(process.cwd(), setupConfig.filename),
|
|
331
|
+
`${setupConfig.tokenContent(authToken)}\n`,
|
|
456
332
|
{ encoding: 'utf8', flag: 'w' },
|
|
457
333
|
);
|
|
458
334
|
clack.log.success(
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
335
|
+
chalk.greenBright(
|
|
336
|
+
`Created ${chalk.bold(
|
|
337
|
+
setupConfig.filename,
|
|
338
|
+
)} with auth token for you to test uploading ${
|
|
339
|
+
setupConfig.name
|
|
340
|
+
} locally.`,
|
|
341
|
+
),
|
|
462
342
|
);
|
|
463
343
|
} catch {
|
|
464
344
|
clack.log.warning(
|
|
465
345
|
`Failed to create ${chalk.bold(
|
|
466
|
-
|
|
467
|
-
)} with auth token. Uploading
|
|
346
|
+
setupConfig.filename,
|
|
347
|
+
)} with auth token. Uploading ${
|
|
348
|
+
setupConfig.name
|
|
349
|
+
} during build will likely not work locally.`,
|
|
468
350
|
);
|
|
469
351
|
}
|
|
470
352
|
}
|
|
471
353
|
|
|
472
|
-
|
|
354
|
+
if (orgSlug && projectSlug) {
|
|
355
|
+
await addOrgAndProjectToSentryCliRc(orgSlug, projectSlug, setupConfig);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
await addAuthTokenFileToGitIgnore(setupConfig.filename);
|
|
473
359
|
}
|
|
474
360
|
|
|
475
361
|
export async function addDotEnvSentryBuildPluginFile(
|
|
@@ -551,7 +437,9 @@ async function addAuthTokenFileToGitIgnore(filename: string): Promise<void> {
|
|
|
551
437
|
{ encoding: 'utf8' },
|
|
552
438
|
);
|
|
553
439
|
clack.log.success(
|
|
554
|
-
|
|
440
|
+
chalk.greenBright(
|
|
441
|
+
`Added ${chalk.bold(filename)} to ${chalk.bold('.gitignore')}.`,
|
|
442
|
+
),
|
|
555
443
|
);
|
|
556
444
|
} catch {
|
|
557
445
|
clack.log.error(
|
|
@@ -607,42 +495,29 @@ export async function getPackageDotJson(): Promise<PackageDotJson> {
|
|
|
607
495
|
return packageJson || {};
|
|
608
496
|
}
|
|
609
497
|
|
|
610
|
-
async function getPackageManager(): Promise<
|
|
611
|
-
const detectedPackageManager =
|
|
498
|
+
async function getPackageManager(): Promise<PackageManager> {
|
|
499
|
+
const detectedPackageManager = detectPackageManger();
|
|
612
500
|
|
|
613
501
|
if (detectedPackageManager) {
|
|
614
502
|
return detectedPackageManager;
|
|
615
503
|
}
|
|
616
504
|
|
|
617
|
-
const selectedPackageManager:
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
505
|
+
const selectedPackageManager: PackageManager | symbol =
|
|
506
|
+
await abortIfCancelled(
|
|
507
|
+
clack.select({
|
|
508
|
+
message: 'Please select your package manager.',
|
|
509
|
+
options: packageManagers.map((packageManager) => ({
|
|
510
|
+
value: packageManager,
|
|
511
|
+
label: packageManager.label,
|
|
512
|
+
})),
|
|
513
|
+
}),
|
|
514
|
+
);
|
|
627
515
|
|
|
628
|
-
Sentry.setTag('package-manager', selectedPackageManager);
|
|
516
|
+
Sentry.setTag('package-manager', selectedPackageManager.name);
|
|
629
517
|
|
|
630
518
|
return selectedPackageManager;
|
|
631
519
|
}
|
|
632
520
|
|
|
633
|
-
export function detectPackageManager(): 'yarn' | 'npm' | 'pnpm' | undefined {
|
|
634
|
-
if (fs.existsSync(path.join(process.cwd(), 'yarn.lock'))) {
|
|
635
|
-
return 'yarn';
|
|
636
|
-
}
|
|
637
|
-
if (fs.existsSync(path.join(process.cwd(), 'package-lock.json'))) {
|
|
638
|
-
return 'npm';
|
|
639
|
-
}
|
|
640
|
-
if (fs.existsSync(path.join(process.cwd(), 'pnpm-lock.yaml'))) {
|
|
641
|
-
return 'pnpm';
|
|
642
|
-
}
|
|
643
|
-
return undefined;
|
|
644
|
-
}
|
|
645
|
-
|
|
646
521
|
export function isUsingTypeScript() {
|
|
647
522
|
try {
|
|
648
523
|
return fs.existsSync(path.join(process.cwd(), 'tsconfig.json'));
|
|
@@ -651,7 +526,26 @@ export function isUsingTypeScript() {
|
|
|
651
526
|
}
|
|
652
527
|
}
|
|
653
528
|
|
|
654
|
-
|
|
529
|
+
/**
|
|
530
|
+
* Checks if we already got project data from a previous wizard invocation.
|
|
531
|
+
* If yes, this data is returned.
|
|
532
|
+
* Otherwise, we start the login flow and ask the user to select a project.
|
|
533
|
+
*
|
|
534
|
+
* Use this function to get project data for the wizard.
|
|
535
|
+
*
|
|
536
|
+
* @param options wizard options
|
|
537
|
+
* @param platform the platform of the wizard
|
|
538
|
+
* @returns project data (org, project, token, url)
|
|
539
|
+
*/
|
|
540
|
+
export async function getOrAskForProjectData(
|
|
541
|
+
options: WizardOptions,
|
|
542
|
+
platform?:
|
|
543
|
+
| 'javascript-nextjs'
|
|
544
|
+
| 'javascript-remix'
|
|
545
|
+
| 'javascript-sveltekit'
|
|
546
|
+
| 'apple-ios'
|
|
547
|
+
| 'android',
|
|
548
|
+
): Promise<{
|
|
655
549
|
sentryUrl: string;
|
|
656
550
|
selfHosted: boolean;
|
|
657
551
|
selectedProject: SentryProjectData;
|
|
@@ -674,10 +568,18 @@ export async function getOrAskForProjectData(options: WizardOptions): Promise<{
|
|
|
674
568
|
askForWizardLogin({
|
|
675
569
|
promoCode: options.promoCode,
|
|
676
570
|
url: sentryUrl,
|
|
677
|
-
platform:
|
|
571
|
+
platform: platform,
|
|
678
572
|
}),
|
|
679
573
|
);
|
|
680
574
|
|
|
575
|
+
if (!projects || !projects.length) {
|
|
576
|
+
clack.log.error(
|
|
577
|
+
'No projects found. Please create a project in Sentry and try again.',
|
|
578
|
+
);
|
|
579
|
+
Sentry.setTag('no-projects-found', true);
|
|
580
|
+
await abort();
|
|
581
|
+
}
|
|
582
|
+
|
|
681
583
|
const selectedProject = await traceStep('select-project', () =>
|
|
682
584
|
askForProjectSelection(projects),
|
|
683
585
|
);
|
|
@@ -689,3 +591,209 @@ export async function getOrAskForProjectData(options: WizardOptions): Promise<{
|
|
|
689
591
|
selectedProject,
|
|
690
592
|
};
|
|
691
593
|
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Asks users if they are using SaaS or self-hosted Sentry and returns the validated URL.
|
|
597
|
+
*
|
|
598
|
+
* If users started the wizard with a --url arg, that URL is used as the default and we skip
|
|
599
|
+
* the self-hosted question. However, the passed url is still validated and in case it's
|
|
600
|
+
* invalid, users are asked to enter a new one until it is valid.
|
|
601
|
+
*
|
|
602
|
+
* @param urlFromArgs the url passed via the --url arg
|
|
603
|
+
*/
|
|
604
|
+
async function askForSelfHosted(urlFromArgs?: string): Promise<{
|
|
605
|
+
url: string;
|
|
606
|
+
selfHosted: boolean;
|
|
607
|
+
}> {
|
|
608
|
+
if (!urlFromArgs) {
|
|
609
|
+
const choice: 'saas' | 'self-hosted' | symbol = await abortIfCancelled(
|
|
610
|
+
clack.select({
|
|
611
|
+
message: 'Are you using Sentry SaaS or self-hosted Sentry?',
|
|
612
|
+
options: [
|
|
613
|
+
{ value: 'saas', label: 'Sentry SaaS (sentry.io)' },
|
|
614
|
+
{
|
|
615
|
+
value: 'self-hosted',
|
|
616
|
+
label: 'Self-hosted/on-premise/single-tenant',
|
|
617
|
+
},
|
|
618
|
+
],
|
|
619
|
+
}),
|
|
620
|
+
);
|
|
621
|
+
|
|
622
|
+
if (choice === 'saas') {
|
|
623
|
+
Sentry.setTag('url', SAAS_URL);
|
|
624
|
+
Sentry.setTag('self-hosted', false);
|
|
625
|
+
return { url: SAAS_URL, selfHosted: false };
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
let validUrl: string | undefined;
|
|
630
|
+
let tmpUrlFromArgs = urlFromArgs;
|
|
631
|
+
|
|
632
|
+
while (validUrl === undefined) {
|
|
633
|
+
const url =
|
|
634
|
+
tmpUrlFromArgs ||
|
|
635
|
+
(await abortIfCancelled(
|
|
636
|
+
clack.text({
|
|
637
|
+
message: `Please enter the URL of your ${
|
|
638
|
+
urlFromArgs ? '' : 'self-hosted '
|
|
639
|
+
}Sentry instance.`,
|
|
640
|
+
placeholder: 'https://sentry.io/',
|
|
641
|
+
}),
|
|
642
|
+
));
|
|
643
|
+
tmpUrlFromArgs = undefined;
|
|
644
|
+
|
|
645
|
+
try {
|
|
646
|
+
validUrl = new URL(url).toString();
|
|
647
|
+
|
|
648
|
+
// We assume everywhere else that the URL ends in a slash
|
|
649
|
+
if (!validUrl.endsWith('/')) {
|
|
650
|
+
validUrl += '/';
|
|
651
|
+
}
|
|
652
|
+
} catch {
|
|
653
|
+
clack.log.error(
|
|
654
|
+
'Please enter a valid URL. (It should look something like "https://sentry.mydomain.com/")',
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const isSelfHostedUrl = new URL(validUrl).host !== new URL(SAAS_URL).host;
|
|
660
|
+
|
|
661
|
+
Sentry.setTag('url', validUrl);
|
|
662
|
+
Sentry.setTag('self-hosted', isSelfHostedUrl);
|
|
663
|
+
|
|
664
|
+
return { url: validUrl, selfHosted: true };
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
async function askForWizardLogin(options: {
|
|
668
|
+
url: string;
|
|
669
|
+
promoCode?: string;
|
|
670
|
+
platform?:
|
|
671
|
+
| 'javascript-nextjs'
|
|
672
|
+
| 'javascript-remix'
|
|
673
|
+
| 'javascript-sveltekit'
|
|
674
|
+
| 'apple-ios'
|
|
675
|
+
| 'android';
|
|
676
|
+
}): Promise<WizardProjectData> {
|
|
677
|
+
Sentry.setTag('has-promo-code', !!options.promoCode);
|
|
678
|
+
|
|
679
|
+
let hasSentryAccount = await clack.confirm({
|
|
680
|
+
message: 'Do you already have a Sentry account?',
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
hasSentryAccount = await abortIfCancelled(hasSentryAccount);
|
|
684
|
+
|
|
685
|
+
Sentry.setTag('already-has-sentry-account', hasSentryAccount);
|
|
686
|
+
|
|
687
|
+
let wizardHash: string;
|
|
688
|
+
try {
|
|
689
|
+
wizardHash = (
|
|
690
|
+
await axios.get<{ hash: string }>(`${options.url}api/0/wizard/`)
|
|
691
|
+
).data.hash;
|
|
692
|
+
} catch {
|
|
693
|
+
if (options.url !== SAAS_URL) {
|
|
694
|
+
clack.log.error('Loading Wizard failed. Did you provide the right URL?');
|
|
695
|
+
await abort(
|
|
696
|
+
chalk.red(
|
|
697
|
+
'Please check your configuration and try again.\n\n Let us know if you think this is an issue with the wizard or Sentry: https://github.com/getsentry/sentry-wizard/issues',
|
|
698
|
+
),
|
|
699
|
+
);
|
|
700
|
+
} else {
|
|
701
|
+
clack.log.error('Loading Wizard failed.');
|
|
702
|
+
await abort(
|
|
703
|
+
chalk.red(
|
|
704
|
+
'Please try again in a few minutes and let us know if this issue persists: https://github.com/getsentry/sentry-wizard/issues',
|
|
705
|
+
),
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
const loginUrl = new URL(
|
|
711
|
+
`${options.url}account/settings/wizard/${wizardHash!}/`,
|
|
712
|
+
);
|
|
713
|
+
|
|
714
|
+
if (!hasSentryAccount) {
|
|
715
|
+
loginUrl.searchParams.set('signup', '1');
|
|
716
|
+
if (options.platform) {
|
|
717
|
+
loginUrl.searchParams.set('project_platform', options.platform);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
if (options.promoCode) {
|
|
722
|
+
loginUrl.searchParams.set('code', options.promoCode);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
const urlToOpen = loginUrl.toString();
|
|
726
|
+
clack.log.info(
|
|
727
|
+
`${chalk.bold(
|
|
728
|
+
`If the browser window didn't open automatically, please open the following link to ${
|
|
729
|
+
hasSentryAccount ? 'log' : 'sign'
|
|
730
|
+
} into Sentry:`,
|
|
731
|
+
)}\n\n${chalk.cyan(urlToOpen)}`,
|
|
732
|
+
);
|
|
733
|
+
|
|
734
|
+
opn(urlToOpen).catch(() => {
|
|
735
|
+
// opn throws in environments that don't have a browser (e.g. remote shells) so we just noop here
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
const loginSpinner = clack.spinner();
|
|
739
|
+
|
|
740
|
+
loginSpinner.start('Waiting for you to log in using the link above');
|
|
741
|
+
|
|
742
|
+
const data = await new Promise<WizardProjectData>((resolve) => {
|
|
743
|
+
const pollingInterval = setInterval(() => {
|
|
744
|
+
axios
|
|
745
|
+
.get<WizardProjectData>(`${options.url}api/0/wizard/${wizardHash}/`, {
|
|
746
|
+
headers: {
|
|
747
|
+
'Accept-Encoding': 'deflate',
|
|
748
|
+
},
|
|
749
|
+
})
|
|
750
|
+
.then((result) => {
|
|
751
|
+
resolve(result.data);
|
|
752
|
+
clearTimeout(timeout);
|
|
753
|
+
clearInterval(pollingInterval);
|
|
754
|
+
void axios.delete(`${options.url}api/0/wizard/${wizardHash}/`);
|
|
755
|
+
})
|
|
756
|
+
.catch(() => {
|
|
757
|
+
// noop - just try again
|
|
758
|
+
});
|
|
759
|
+
}, 500);
|
|
760
|
+
|
|
761
|
+
const timeout = setTimeout(() => {
|
|
762
|
+
clearInterval(pollingInterval);
|
|
763
|
+
loginSpinner.stop(
|
|
764
|
+
'Login timed out. No worries - it happens to the best of us.',
|
|
765
|
+
);
|
|
766
|
+
|
|
767
|
+
Sentry.setTag('opened-wizard-link', false);
|
|
768
|
+
void abort('Please restart the Wizard and log in to complete the setup.');
|
|
769
|
+
}, 180_000);
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
loginSpinner.stop('Login complete.');
|
|
773
|
+
Sentry.setTag('opened-wizard-link', true);
|
|
774
|
+
|
|
775
|
+
return data;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
async function askForProjectSelection(
|
|
779
|
+
projects: SentryProjectData[],
|
|
780
|
+
): Promise<SentryProjectData> {
|
|
781
|
+
const selection: SentryProjectData | symbol = await abortIfCancelled(
|
|
782
|
+
clack.select({
|
|
783
|
+
maxItems: 12,
|
|
784
|
+
message: 'Select your Sentry project.',
|
|
785
|
+
options: projects.map((project) => {
|
|
786
|
+
return {
|
|
787
|
+
value: project,
|
|
788
|
+
label: `${project.organization.slug}/${project.slug}`,
|
|
789
|
+
};
|
|
790
|
+
}),
|
|
791
|
+
}),
|
|
792
|
+
);
|
|
793
|
+
|
|
794
|
+
Sentry.setTag('project', selection.slug);
|
|
795
|
+
Sentry.setTag('project-platform', selection.platform);
|
|
796
|
+
Sentry.setUser({ id: selection.organization.slug });
|
|
797
|
+
|
|
798
|
+
return selection;
|
|
799
|
+
}
|