@sentry/wizard 3.10.0 → 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 +47 -7
- package/dist/lib/Constants.d.ts +1 -0
- package/dist/lib/Constants.js +5 -0
- package/dist/lib/Constants.js.map +1 -1
- package/dist/lib/Steps/ChooseIntegration.js +8 -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/ReactNative.js +3 -3
- package/dist/lib/Steps/Integrations/ReactNative.js.map +1 -1
- 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/remix-wizard.js +10 -20
- package/dist/src/remix/remix-wizard.js.map +1 -1
- package/dist/src/sourcemaps/sourcemaps-wizard.js +26 -13
- 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/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.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 +22 -28
- package/dist/src/utils/clack-utils.js +270 -264
- 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 +5 -0
- package/lib/Steps/ChooseIntegration.ts +7 -3
- package/lib/Steps/Integrations/Android.ts +23 -0
- package/lib/Steps/Integrations/ReactNative.ts +9 -3
- 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/remix-wizard.ts +10 -15
- package/src/sourcemaps/sourcemaps-wizard.ts +19 -5
- package/src/sourcemaps/tools/nextjs.ts +2 -2
- package/src/sourcemaps/tools/sentry-cli.ts +8 -7
- package/src/sourcemaps/tools/vite.ts +136 -6
- package/src/sourcemaps/utils/detect-tool.ts +2 -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 +337 -283
- 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();
|
|
@@ -134,119 +171,13 @@ export async function askToInstallSentryCLI(): Promise<boolean> {
|
|
|
134
171
|
);
|
|
135
172
|
}
|
|
136
173
|
|
|
137
|
-
export async function askForWizardLogin(options: {
|
|
138
|
-
url: string;
|
|
139
|
-
promoCode?: string;
|
|
140
|
-
platform?:
|
|
141
|
-
| 'javascript-nextjs'
|
|
142
|
-
| 'javascript-remix'
|
|
143
|
-
| 'javascript-sveltekit'
|
|
144
|
-
| 'apple-ios';
|
|
145
|
-
}): Promise<WizardProjectData> {
|
|
146
|
-
Sentry.setTag('has-promo-code', !!options.promoCode);
|
|
147
|
-
|
|
148
|
-
let hasSentryAccount = await clack.confirm({
|
|
149
|
-
message: 'Do you already have a Sentry account?',
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
hasSentryAccount = await abortIfCancelled(hasSentryAccount);
|
|
153
|
-
|
|
154
|
-
Sentry.setTag('already-has-sentry-account', hasSentryAccount);
|
|
155
|
-
|
|
156
|
-
let wizardHash: string;
|
|
157
|
-
try {
|
|
158
|
-
wizardHash = (
|
|
159
|
-
await axios.get<{ hash: string }>(`${options.url}api/0/wizard/`)
|
|
160
|
-
).data.hash;
|
|
161
|
-
} catch {
|
|
162
|
-
if (options.url !== SAAS_URL) {
|
|
163
|
-
clack.log.error('Loading Wizard failed. Did you provide the right URL?');
|
|
164
|
-
await abort(
|
|
165
|
-
chalk.red(
|
|
166
|
-
'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',
|
|
167
|
-
),
|
|
168
|
-
);
|
|
169
|
-
} else {
|
|
170
|
-
clack.log.error('Loading Wizard failed.');
|
|
171
|
-
await abort(
|
|
172
|
-
chalk.red(
|
|
173
|
-
'Please try again in a few minutes and let us know if this issue persists: https://github.com/getsentry/sentry-wizard/issues',
|
|
174
|
-
),
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const loginUrl = new URL(
|
|
180
|
-
`${options.url}account/settings/wizard/${wizardHash!}/`,
|
|
181
|
-
);
|
|
182
|
-
|
|
183
|
-
if (!hasSentryAccount) {
|
|
184
|
-
loginUrl.searchParams.set('signup', '1');
|
|
185
|
-
if (options.platform) {
|
|
186
|
-
loginUrl.searchParams.set('project_platform', options.platform);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (options.promoCode) {
|
|
191
|
-
loginUrl.searchParams.set('code', options.promoCode);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const urlToOpen = loginUrl.toString();
|
|
195
|
-
clack.log.info(
|
|
196
|
-
`${chalk.bold(
|
|
197
|
-
`If the browser window didn't open automatically, please open the following link to ${
|
|
198
|
-
hasSentryAccount ? 'log' : 'sign'
|
|
199
|
-
} into Sentry:`,
|
|
200
|
-
)}\n\n${chalk.cyan(urlToOpen)}`,
|
|
201
|
-
);
|
|
202
|
-
|
|
203
|
-
opn(urlToOpen).catch(() => {
|
|
204
|
-
// opn throws in environments that don't have a browser (e.g. remote shells) so we just noop here
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
const loginSpinner = clack.spinner();
|
|
208
|
-
|
|
209
|
-
loginSpinner.start('Waiting for you to log in using the link above');
|
|
210
|
-
|
|
211
|
-
const data = await new Promise<WizardProjectData>((resolve) => {
|
|
212
|
-
const pollingInterval = setInterval(() => {
|
|
213
|
-
axios
|
|
214
|
-
.get<WizardProjectData>(`${options.url}api/0/wizard/${wizardHash}/`)
|
|
215
|
-
.then((result) => {
|
|
216
|
-
resolve(result.data);
|
|
217
|
-
clearTimeout(timeout);
|
|
218
|
-
clearInterval(pollingInterval);
|
|
219
|
-
void axios.delete(`${options.url}api/0/wizard/${wizardHash}/`);
|
|
220
|
-
})
|
|
221
|
-
.catch(() => {
|
|
222
|
-
// noop - just try again
|
|
223
|
-
});
|
|
224
|
-
}, 500);
|
|
225
|
-
|
|
226
|
-
const timeout = setTimeout(() => {
|
|
227
|
-
clearInterval(pollingInterval);
|
|
228
|
-
loginSpinner.stop(
|
|
229
|
-
'Login timed out. No worries - it happens to the best of us.',
|
|
230
|
-
);
|
|
231
|
-
|
|
232
|
-
Sentry.setTag('opened-wizard-link', false);
|
|
233
|
-
void abort('Please restart the Wizard and log in to complete the setup.');
|
|
234
|
-
}, 180_000);
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
loginSpinner.stop('Login complete.');
|
|
238
|
-
Sentry.setTag('opened-wizard-link', true);
|
|
239
|
-
|
|
240
|
-
return data;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
174
|
export async function askForItemSelection(
|
|
244
175
|
items: string[],
|
|
245
176
|
message: string,
|
|
246
177
|
): Promise<{ value: string; index: number }> {
|
|
247
178
|
const selection: { value: string; index: number } | symbol =
|
|
248
179
|
await abortIfCancelled(
|
|
249
|
-
|
|
180
|
+
clack.select({
|
|
250
181
|
maxItems: 12,
|
|
251
182
|
message: message,
|
|
252
183
|
options: items.map((item, index) => {
|
|
@@ -261,29 +192,6 @@ export async function askForItemSelection(
|
|
|
261
192
|
return selection;
|
|
262
193
|
}
|
|
263
194
|
|
|
264
|
-
export async function askForProjectSelection(
|
|
265
|
-
projects: SentryProjectData[],
|
|
266
|
-
): Promise<SentryProjectData> {
|
|
267
|
-
const selection: SentryProjectData | symbol = await abortIfCancelled(
|
|
268
|
-
windowedSelect({
|
|
269
|
-
maxItems: 12,
|
|
270
|
-
message: 'Select your Sentry project.',
|
|
271
|
-
options: projects.map((project) => {
|
|
272
|
-
return {
|
|
273
|
-
value: project,
|
|
274
|
-
label: `${project.organization.slug}/${project.slug}`,
|
|
275
|
-
};
|
|
276
|
-
}),
|
|
277
|
-
}),
|
|
278
|
-
);
|
|
279
|
-
|
|
280
|
-
Sentry.setTag('project', selection.slug);
|
|
281
|
-
Sentry.setTag('project-platform', selection.platform);
|
|
282
|
-
Sentry.setUser({ id: selection.organization.slug });
|
|
283
|
-
|
|
284
|
-
return selection;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
195
|
export async function installPackage({
|
|
288
196
|
packageName,
|
|
289
197
|
alreadyInstalled,
|
|
@@ -314,17 +222,11 @@ export async function installPackage({
|
|
|
314
222
|
sdkInstallSpinner.start(
|
|
315
223
|
`${alreadyInstalled ? 'Updating' : 'Installing'} ${chalk.bold.cyan(
|
|
316
224
|
packageName,
|
|
317
|
-
)} with ${chalk.bold(packageManager)}.`,
|
|
225
|
+
)} with ${chalk.bold(packageManager.label)}.`,
|
|
318
226
|
);
|
|
319
227
|
|
|
320
228
|
try {
|
|
321
|
-
|
|
322
|
-
await promisify(childProcess.exec)(`yarn add ${packageName}@latest`);
|
|
323
|
-
} else if (packageManager === 'pnpm') {
|
|
324
|
-
await promisify(childProcess.exec)(`pnpm add ${packageName}@latest`);
|
|
325
|
-
} else if (packageManager === 'npm') {
|
|
326
|
-
await promisify(childProcess.exec)(`npm install ${packageName}@latest`);
|
|
327
|
-
}
|
|
229
|
+
await installPackageWithPackageManager(packageManager, packageName);
|
|
328
230
|
} catch (e) {
|
|
329
231
|
sdkInstallSpinner.stop('Installation failed.');
|
|
330
232
|
clack.log.error(
|
|
@@ -341,189 +243,119 @@ export async function installPackage({
|
|
|
341
243
|
sdkInstallSpinner.stop(
|
|
342
244
|
`${alreadyInstalled ? 'Updated' : 'Installed'} ${chalk.bold.cyan(
|
|
343
245
|
packageName,
|
|
344
|
-
)} with ${chalk.bold(packageManager)}.`,
|
|
246
|
+
)} with ${chalk.bold(packageManager.label)}.`,
|
|
345
247
|
);
|
|
346
248
|
}
|
|
347
249
|
|
|
348
|
-
/**
|
|
349
|
-
* Asks users if they are using SaaS or self-hosted Sentry and returns the validated URL.
|
|
350
|
-
*
|
|
351
|
-
* If users started the wizard with a --url arg, that URL is used as the default and we skip
|
|
352
|
-
* the self-hosted question. However, the passed url is still validated and in case it's
|
|
353
|
-
* invalid, users are asked to enter a new one until it is valid.
|
|
354
|
-
*
|
|
355
|
-
* @param urlFromArgs the url passed via the --url arg
|
|
356
|
-
*/
|
|
357
|
-
export async function askForSelfHosted(urlFromArgs?: string): Promise<{
|
|
358
|
-
url: string;
|
|
359
|
-
selfHosted: boolean;
|
|
360
|
-
}> {
|
|
361
|
-
if (!urlFromArgs) {
|
|
362
|
-
const choice: 'saas' | 'self-hosted' | symbol = await abortIfCancelled(
|
|
363
|
-
clack.select({
|
|
364
|
-
message: 'Are you using Sentry SaaS or self-hosted Sentry?',
|
|
365
|
-
options: [
|
|
366
|
-
{ value: 'saas', label: 'Sentry SaaS (sentry.io)' },
|
|
367
|
-
{
|
|
368
|
-
value: 'self-hosted',
|
|
369
|
-
label: 'Self-hosted/on-premise/single-tenant',
|
|
370
|
-
},
|
|
371
|
-
],
|
|
372
|
-
}),
|
|
373
|
-
);
|
|
374
|
-
|
|
375
|
-
if (choice === 'saas') {
|
|
376
|
-
Sentry.setTag('url', SAAS_URL);
|
|
377
|
-
Sentry.setTag('self-hosted', false);
|
|
378
|
-
return { url: SAAS_URL, selfHosted: false };
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
let validUrl: string | undefined;
|
|
383
|
-
let tmpUrlFromArgs = urlFromArgs;
|
|
384
|
-
|
|
385
|
-
while (validUrl === undefined) {
|
|
386
|
-
const url =
|
|
387
|
-
tmpUrlFromArgs ||
|
|
388
|
-
(await abortIfCancelled(
|
|
389
|
-
clack.text({
|
|
390
|
-
message: `Please enter the URL of your ${
|
|
391
|
-
urlFromArgs ? '' : 'self-hosted '
|
|
392
|
-
}Sentry instance.`,
|
|
393
|
-
placeholder: 'https://sentry.io/',
|
|
394
|
-
}),
|
|
395
|
-
));
|
|
396
|
-
tmpUrlFromArgs = undefined;
|
|
397
|
-
|
|
398
|
-
try {
|
|
399
|
-
validUrl = new URL(url).toString();
|
|
400
|
-
|
|
401
|
-
// We assume everywhere else that the URL ends in a slash
|
|
402
|
-
if (!validUrl.endsWith('/')) {
|
|
403
|
-
validUrl += '/';
|
|
404
|
-
}
|
|
405
|
-
} catch {
|
|
406
|
-
clack.log.error(
|
|
407
|
-
'Please enter a valid URL. (It should look something like "https://sentry.mydomain.com/")',
|
|
408
|
-
);
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
const isSelfHostedUrl = new URL(validUrl).host !== new URL(SAAS_URL).host;
|
|
413
|
-
|
|
414
|
-
Sentry.setTag('url', validUrl);
|
|
415
|
-
Sentry.setTag('self-hosted', isSelfHostedUrl);
|
|
416
|
-
|
|
417
|
-
return { url: validUrl, selfHosted: true };
|
|
418
|
-
}
|
|
419
|
-
|
|
420
250
|
async function addOrgAndProjectToSentryCliRc(
|
|
421
251
|
org: string,
|
|
422
252
|
project: string,
|
|
253
|
+
setupConfig: CliSetupConfig,
|
|
423
254
|
): Promise<void> {
|
|
424
|
-
const
|
|
425
|
-
path.join(process.cwd(),
|
|
255
|
+
const configContents = fs.readFileSync(
|
|
256
|
+
path.join(process.cwd(), setupConfig.filename),
|
|
426
257
|
'utf8',
|
|
427
258
|
);
|
|
428
259
|
|
|
429
|
-
|
|
430
|
-
clircContents.includes('[defaults]') &&
|
|
431
|
-
clircContents.match(/org=./g) &&
|
|
432
|
-
clircContents.match(/project=./g)
|
|
433
|
-
);
|
|
434
|
-
|
|
435
|
-
if (likelyAlreadyHasOrgAndProject) {
|
|
260
|
+
if (setupConfig.likelyAlreadyHasOrgAndProject(configContents)) {
|
|
436
261
|
clack.log.warn(
|
|
437
262
|
`${chalk.bold(
|
|
438
|
-
|
|
263
|
+
setupConfig.filename,
|
|
439
264
|
)} already has org and project. Will not add them.`,
|
|
440
265
|
);
|
|
441
266
|
} else {
|
|
442
267
|
try {
|
|
443
268
|
await fs.promises.appendFile(
|
|
444
|
-
path.join(process.cwd(),
|
|
445
|
-
`\n
|
|
269
|
+
path.join(process.cwd(), setupConfig.filename),
|
|
270
|
+
`\n${setupConfig.orgAndProjContent(org, project)}\n`,
|
|
446
271
|
);
|
|
447
272
|
} catch (e) {
|
|
448
273
|
clack.log.warn(
|
|
449
274
|
`${chalk.bold(
|
|
450
|
-
|
|
275
|
+
setupConfig.filename,
|
|
451
276
|
)} could not be updated with org and project.`,
|
|
452
277
|
);
|
|
453
278
|
}
|
|
454
279
|
}
|
|
455
280
|
}
|
|
456
281
|
|
|
457
|
-
export async function
|
|
282
|
+
export async function addSentryCliConfig(
|
|
458
283
|
authToken: string,
|
|
284
|
+
setupConfig: CliSetupConfig = sourceMapsCliSetupConfig,
|
|
459
285
|
orgSlug?: string,
|
|
460
286
|
projectSlug?: string,
|
|
461
287
|
): Promise<void> {
|
|
462
|
-
const
|
|
463
|
-
path.join(process.cwd(),
|
|
288
|
+
const configExists = fs.existsSync(
|
|
289
|
+
path.join(process.cwd(), setupConfig.filename),
|
|
464
290
|
);
|
|
465
|
-
if (
|
|
466
|
-
const
|
|
467
|
-
path.join(process.cwd(),
|
|
291
|
+
if (configExists) {
|
|
292
|
+
const configContents = fs.readFileSync(
|
|
293
|
+
path.join(process.cwd(), setupConfig.filename),
|
|
468
294
|
'utf8',
|
|
469
295
|
);
|
|
470
296
|
|
|
471
|
-
|
|
472
|
-
clircContents.includes('[auth]') && clircContents.match(/token=./g)
|
|
473
|
-
);
|
|
474
|
-
|
|
475
|
-
if (likelyAlreadyHasAuthToken) {
|
|
297
|
+
if (setupConfig.likelyAlreadyHasAuthToken(configContents)) {
|
|
476
298
|
clack.log.warn(
|
|
477
299
|
`${chalk.bold(
|
|
478
|
-
|
|
300
|
+
setupConfig.filename,
|
|
479
301
|
)} already has auth token. Will not add one.`,
|
|
480
302
|
);
|
|
481
303
|
} else {
|
|
482
304
|
try {
|
|
483
305
|
await fs.promises.writeFile(
|
|
484
|
-
path.join(process.cwd(),
|
|
485
|
-
`${
|
|
306
|
+
path.join(process.cwd(), setupConfig.filename),
|
|
307
|
+
`${configContents}\n${setupConfig.tokenContent(authToken)}\n`,
|
|
486
308
|
{ encoding: 'utf8', flag: 'w' },
|
|
487
309
|
);
|
|
488
310
|
clack.log.success(
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
311
|
+
chalk.greenBright(
|
|
312
|
+
`Added auth token to ${chalk.bold(
|
|
313
|
+
setupConfig.filename,
|
|
314
|
+
)} for you to test uploading ${setupConfig.name} locally.`,
|
|
315
|
+
),
|
|
492
316
|
);
|
|
493
317
|
} catch {
|
|
494
318
|
clack.log.warning(
|
|
495
319
|
`Failed to add auth token to ${chalk.bold(
|
|
496
|
-
|
|
497
|
-
)}. Uploading
|
|
320
|
+
setupConfig.filename,
|
|
321
|
+
)}. Uploading ${
|
|
322
|
+
setupConfig.name
|
|
323
|
+
} during build will likely not work locally.`,
|
|
498
324
|
);
|
|
499
325
|
}
|
|
500
326
|
}
|
|
501
327
|
} else {
|
|
502
328
|
try {
|
|
503
329
|
await fs.promises.writeFile(
|
|
504
|
-
path.join(process.cwd(),
|
|
505
|
-
|
|
330
|
+
path.join(process.cwd(), setupConfig.filename),
|
|
331
|
+
`${setupConfig.tokenContent(authToken)}\n`,
|
|
506
332
|
{ encoding: 'utf8', flag: 'w' },
|
|
507
333
|
);
|
|
508
334
|
clack.log.success(
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
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
|
+
),
|
|
512
342
|
);
|
|
513
343
|
} catch {
|
|
514
344
|
clack.log.warning(
|
|
515
345
|
`Failed to create ${chalk.bold(
|
|
516
|
-
|
|
517
|
-
)} with auth token. Uploading
|
|
346
|
+
setupConfig.filename,
|
|
347
|
+
)} with auth token. Uploading ${
|
|
348
|
+
setupConfig.name
|
|
349
|
+
} during build will likely not work locally.`,
|
|
518
350
|
);
|
|
519
351
|
}
|
|
520
352
|
}
|
|
521
353
|
|
|
522
354
|
if (orgSlug && projectSlug) {
|
|
523
|
-
await addOrgAndProjectToSentryCliRc(orgSlug, projectSlug);
|
|
355
|
+
await addOrgAndProjectToSentryCliRc(orgSlug, projectSlug, setupConfig);
|
|
524
356
|
}
|
|
525
357
|
|
|
526
|
-
await addAuthTokenFileToGitIgnore(
|
|
358
|
+
await addAuthTokenFileToGitIgnore(setupConfig.filename);
|
|
527
359
|
}
|
|
528
360
|
|
|
529
361
|
export async function addDotEnvSentryBuildPluginFile(
|
|
@@ -605,7 +437,9 @@ async function addAuthTokenFileToGitIgnore(filename: string): Promise<void> {
|
|
|
605
437
|
{ encoding: 'utf8' },
|
|
606
438
|
);
|
|
607
439
|
clack.log.success(
|
|
608
|
-
|
|
440
|
+
chalk.greenBright(
|
|
441
|
+
`Added ${chalk.bold(filename)} to ${chalk.bold('.gitignore')}.`,
|
|
442
|
+
),
|
|
609
443
|
);
|
|
610
444
|
} catch {
|
|
611
445
|
clack.log.error(
|
|
@@ -661,42 +495,29 @@ export async function getPackageDotJson(): Promise<PackageDotJson> {
|
|
|
661
495
|
return packageJson || {};
|
|
662
496
|
}
|
|
663
497
|
|
|
664
|
-
async function getPackageManager(): Promise<
|
|
665
|
-
const detectedPackageManager =
|
|
498
|
+
async function getPackageManager(): Promise<PackageManager> {
|
|
499
|
+
const detectedPackageManager = detectPackageManger();
|
|
666
500
|
|
|
667
501
|
if (detectedPackageManager) {
|
|
668
502
|
return detectedPackageManager;
|
|
669
503
|
}
|
|
670
504
|
|
|
671
|
-
const selectedPackageManager:
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
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
|
+
);
|
|
681
515
|
|
|
682
|
-
Sentry.setTag('package-manager', selectedPackageManager);
|
|
516
|
+
Sentry.setTag('package-manager', selectedPackageManager.name);
|
|
683
517
|
|
|
684
518
|
return selectedPackageManager;
|
|
685
519
|
}
|
|
686
520
|
|
|
687
|
-
export function detectPackageManager(): 'yarn' | 'npm' | 'pnpm' | undefined {
|
|
688
|
-
if (fs.existsSync(path.join(process.cwd(), 'yarn.lock'))) {
|
|
689
|
-
return 'yarn';
|
|
690
|
-
}
|
|
691
|
-
if (fs.existsSync(path.join(process.cwd(), 'package-lock.json'))) {
|
|
692
|
-
return 'npm';
|
|
693
|
-
}
|
|
694
|
-
if (fs.existsSync(path.join(process.cwd(), 'pnpm-lock.yaml'))) {
|
|
695
|
-
return 'pnpm';
|
|
696
|
-
}
|
|
697
|
-
return undefined;
|
|
698
|
-
}
|
|
699
|
-
|
|
700
521
|
export function isUsingTypeScript() {
|
|
701
522
|
try {
|
|
702
523
|
return fs.existsSync(path.join(process.cwd(), 'tsconfig.json'));
|
|
@@ -705,7 +526,26 @@ export function isUsingTypeScript() {
|
|
|
705
526
|
}
|
|
706
527
|
}
|
|
707
528
|
|
|
708
|
-
|
|
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<{
|
|
709
549
|
sentryUrl: string;
|
|
710
550
|
selfHosted: boolean;
|
|
711
551
|
selectedProject: SentryProjectData;
|
|
@@ -728,10 +568,18 @@ export async function getOrAskForProjectData(options: WizardOptions): Promise<{
|
|
|
728
568
|
askForWizardLogin({
|
|
729
569
|
promoCode: options.promoCode,
|
|
730
570
|
url: sentryUrl,
|
|
731
|
-
platform:
|
|
571
|
+
platform: platform,
|
|
732
572
|
}),
|
|
733
573
|
);
|
|
734
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
|
+
|
|
735
583
|
const selectedProject = await traceStep('select-project', () =>
|
|
736
584
|
askForProjectSelection(projects),
|
|
737
585
|
);
|
|
@@ -743,3 +591,209 @@ export async function getOrAskForProjectData(options: WizardOptions): Promise<{
|
|
|
743
591
|
selectedProject,
|
|
744
592
|
};
|
|
745
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
|
+
}
|