@sentry/wizard 3.16.3 → 3.16.5

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.
@@ -23,15 +23,18 @@ import {
23
23
  } from '../utils/clack-utils';
24
24
  import { SentryProjectData, WizardOptions } from '../utils/types';
25
25
  import {
26
+ getFullUnderscoreErrorCopyPasteSnippet,
26
27
  getNextjsConfigCjsAppendix,
27
28
  getNextjsConfigCjsTemplate,
28
29
  getNextjsConfigEsmCopyPasteSnippet,
29
30
  getNextjsSentryBuildOptionsTemplate,
30
31
  getNextjsWebpackPluginOptionsTemplate,
31
32
  getSentryConfigContents,
33
+ getSentryDefaultUnderscoreErrorPage,
32
34
  getSentryExampleApiRoute,
33
35
  getSentryExampleAppDirApiRoute,
34
36
  getSentryExamplePageContents,
37
+ getSimpleUnderscoreErrorCopyPasteSnippet,
35
38
  } from './templates';
36
39
  import { traceStep, withTelemetry } from '../telemetry';
37
40
  import { getPackageVersion, hasPackageInstalled } from '../utils/package-json';
@@ -83,6 +86,109 @@ export async function runNextjsWizardWithTelemetry(
83
86
  createOrMergeNextJsFiles(selectedProject, selfHosted, sentryUrl),
84
87
  );
85
88
 
89
+ await traceStep('create-underscoreerror-page', async () => {
90
+ const srcDir = path.join(process.cwd(), 'src');
91
+ const maybePagesDirPath = path.join(process.cwd(), 'pages');
92
+ const maybeSrcPagesDirPath = path.join(srcDir, 'pages');
93
+
94
+ const pagesLocation =
95
+ fs.existsSync(maybePagesDirPath) &&
96
+ fs.lstatSync(maybePagesDirPath).isDirectory()
97
+ ? ['pages']
98
+ : fs.existsSync(maybeSrcPagesDirPath) &&
99
+ fs.lstatSync(maybeSrcPagesDirPath).isDirectory()
100
+ ? ['src', 'pages']
101
+ : undefined;
102
+
103
+ if (!pagesLocation) {
104
+ return;
105
+ }
106
+
107
+ const underscoreErrorPageFile = fs.existsSync(
108
+ path.join(process.cwd(), ...pagesLocation, '_error.tsx'),
109
+ )
110
+ ? '_error.tsx'
111
+ : fs.existsSync(path.join(process.cwd(), ...pagesLocation, '_error.ts'))
112
+ ? '_error.ts'
113
+ : fs.existsSync(path.join(process.cwd(), ...pagesLocation, '_error.jsx'))
114
+ ? '_error.jsx'
115
+ : fs.existsSync(path.join(process.cwd(), ...pagesLocation, '_error.js'))
116
+ ? '_error.js'
117
+ : undefined;
118
+
119
+ if (!underscoreErrorPageFile) {
120
+ await fs.promises.writeFile(
121
+ path.join(process.cwd(), ...pagesLocation, '_error.jsx'),
122
+ getSentryDefaultUnderscoreErrorPage(),
123
+ { encoding: 'utf8', flag: 'w' },
124
+ );
125
+
126
+ clack.log.success(
127
+ `Created ${chalk.bold(path.join(...pagesLocation, '_error.jsx'))}.`,
128
+ );
129
+ } else if (
130
+ fs
131
+ .readFileSync(
132
+ path.join(process.cwd(), ...pagesLocation, underscoreErrorPageFile),
133
+ 'utf8',
134
+ )
135
+ .includes('getInitialProps')
136
+ ) {
137
+ clack.log.info(
138
+ `It seems like you already have a custom error page.\n\nPlease put the following function call in the ${chalk.bold(
139
+ 'getInitialProps',
140
+ )}\nmethod of your custom error page at ${chalk.bold(
141
+ path.join(...pagesLocation, underscoreErrorPageFile),
142
+ )}:`,
143
+ );
144
+
145
+ // eslint-disable-next-line no-console
146
+ console.log(getSimpleUnderscoreErrorCopyPasteSnippet());
147
+
148
+ const shouldContinue = await abortIfCancelled(
149
+ clack.confirm({
150
+ message: `Did you modify your ${chalk.bold(
151
+ path.join(...pagesLocation, underscoreErrorPageFile),
152
+ )} file as described above?`,
153
+ active: 'Yes',
154
+ inactive: 'No, get me out of here',
155
+ }),
156
+ );
157
+
158
+ if (!shouldContinue) {
159
+ await abort();
160
+ }
161
+ } else {
162
+ clack.log.info(
163
+ `It seems like you already have a custom error page.\n\nPlease add the following code to your custom error page\nat ${chalk.bold(
164
+ path.join(...pagesLocation, underscoreErrorPageFile),
165
+ )}:`,
166
+ );
167
+
168
+ // eslint-disable-next-line no-console
169
+ console.log(
170
+ getFullUnderscoreErrorCopyPasteSnippet(
171
+ underscoreErrorPageFile === '_error.ts' ||
172
+ underscoreErrorPageFile === '_error.tsx',
173
+ ),
174
+ );
175
+
176
+ const shouldContinue = await abortIfCancelled(
177
+ clack.confirm({
178
+ message: `Did add the code to your ${chalk.bold(
179
+ path.join(...pagesLocation, underscoreErrorPageFile),
180
+ )} file as described above?`,
181
+ active: 'Yes',
182
+ inactive: 'No, get me out of here',
183
+ }),
184
+ );
185
+
186
+ if (!shouldContinue) {
187
+ await abort();
188
+ }
189
+ }
190
+ });
191
+
86
192
  await traceStep('create-example-page', async () =>
87
193
  createExamplePage(selfHosted, selectedProject, sentryUrl),
88
194
  );
@@ -1,3 +1,5 @@
1
+ import chalk from 'chalk';
2
+
1
3
  export function getNextjsWebpackPluginOptionsTemplate(
2
4
  orgSlug: string,
3
5
  projectSlug: string,
@@ -267,3 +269,58 @@ export function GET() {
267
269
  }
268
270
  `;
269
271
  }
272
+
273
+ export function getSentryDefaultUnderscoreErrorPage() {
274
+ return `import * as Sentry from "@sentry/nextjs";
275
+ import Error from "next/error";
276
+
277
+ const CustomErrorComponent = (props) => {
278
+ return <Error statusCode={props.statusCode} />;
279
+ };
280
+
281
+ CustomErrorComponent.getInitialProps = async (contextData) => {
282
+ // In case this is running in a serverless function, await this in order to give Sentry
283
+ // time to send the error before the lambda exits
284
+ await Sentry.captureUnderscoreErrorException(contextData);
285
+
286
+ // This will contain the status code of the response
287
+ return Error.getInitialProps(contextData);
288
+ };
289
+
290
+ export default CustomErrorComponent;
291
+ `;
292
+ }
293
+
294
+ export function getSimpleUnderscoreErrorCopyPasteSnippet() {
295
+ return `
296
+ ${chalk.green(`import * as Sentry from '@sentry/nextjs';`)}
297
+
298
+ ${chalk.dim(
299
+ '// Replace "YourCustomErrorComponent" with your custom error component!',
300
+ )}
301
+ YourCustomErrorComponent.getInitialProps = async (${chalk.green(
302
+ `contextData`,
303
+ )}) => {
304
+ ${chalk.green('await Sentry.captureUnderscoreErrorException(contextData);')}
305
+
306
+ ${chalk.dim('// ...other getInitialProps code')}
307
+ };
308
+ `;
309
+ }
310
+
311
+ export function getFullUnderscoreErrorCopyPasteSnippet(isTs: boolean) {
312
+ return `
313
+ import * as Sentry from '@sentry/nextjs';${
314
+ isTs ? '\nimport type { NextPageContext } from "next";' : ''
315
+ }
316
+
317
+ ${chalk.dim(
318
+ '// Replace "YourCustomErrorComponent" with your custom error component!',
319
+ )}
320
+ YourCustomErrorComponent.getInitialProps = async (contextData${
321
+ isTs ? ': NextPageContext' : ''
322
+ }) => {
323
+ await Sentry.captureUnderscoreErrorException(contextData);
324
+ };
325
+ `;
326
+ }
@@ -37,7 +37,6 @@ import {
37
37
  import { runReactNativeUninstall } from './uninstall';
38
38
  import { APP_BUILD_GRADLE, XCODE_PROJECT, getFirstMatchedPath } from './glob';
39
39
  import { ReactNativeWizardOptions } from './options';
40
- import { SentryProjectData } from '../utils/types';
41
40
  import {
42
41
  addSentryInitWithSdkImport,
43
42
  doesJsCodeIncludeSdkSentryImport,
@@ -45,6 +44,7 @@ import {
45
44
  } from './javascript';
46
45
  import { traceStep, withTelemetry } from '../telemetry';
47
46
  import * as Sentry from '@sentry/node';
47
+ import { getIssueStreamUrl } from '../utils/url';
48
48
 
49
49
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
50
50
  const xcode = require('xcode');
@@ -107,6 +107,7 @@ export async function runReactNativeWizardWithTelemetry(
107
107
  await getOrAskForProjectData(options, 'react-native');
108
108
  const orgSlug = selectedProject.organization.slug;
109
109
  const projectSlug = selectedProject.slug;
110
+ const projectId = selectedProject.id;
110
111
  const cliConfig: RNCliSetupConfigContent = {
111
112
  authToken,
112
113
  org: orgSlug,
@@ -134,7 +135,9 @@ export async function runReactNativeWizardWithTelemetry(
134
135
  }
135
136
 
136
137
  const confirmedFirstException = await confirmFirstSentryException(
137
- selectedProject,
138
+ sentryUrl,
139
+ orgSlug,
140
+ projectId,
138
141
  );
139
142
  Sentry.setTag('user-confirmed-first-error', confirmedFirstException);
140
143
 
@@ -207,8 +210,12 @@ async function addSentryInit({ dsn }: { dsn: string }) {
207
210
  );
208
211
  }
209
212
 
210
- async function confirmFirstSentryException(project: SentryProjectData) {
211
- const projectsIssuesUrl = `${project.organization.links.organizationUrl}/issues/?project=${project.id}`;
213
+ async function confirmFirstSentryException(
214
+ url: string,
215
+ orgSlug: string,
216
+ projectId: string,
217
+ ) {
218
+ const issuesStreamUrl = getIssueStreamUrl({ url, orgSlug, projectId });
212
219
 
213
220
  clack.log
214
221
  .step(`To make sure everything is set up correctly, put the following code snippet into your application.
@@ -216,7 +223,7 @@ The snippet will create a button that, when tapped, sends a test event to Sentry
216
223
 
217
224
  After that check your project issues:
218
225
 
219
- ${chalk.cyan(projectsIssuesUrl)}`);
226
+ ${chalk.cyan(issuesStreamUrl)}`);
220
227
 
221
228
  // We want the code snippet to be easily copy-pasteable, without any clack artifacts
222
229
  // eslint-disable-next-line no-console
@@ -74,7 +74,7 @@ async function runRemixWizardWithTelemetry(
74
74
  try {
75
75
  await updateBuildScript({
76
76
  org: selectedProject.organization.slug,
77
- project: selectedProject.name,
77
+ project: selectedProject.slug,
78
78
  url: sentryUrl === DEFAULT_URL ? undefined : sentryUrl,
79
79
  isHydrogen: isHydrogenApp(packageJson),
80
80
  });
@@ -24,13 +24,13 @@ import { WizardOptions } from '../utils/types';
24
24
  import { configureCRASourcemapGenerationFlow } from './tools/create-react-app';
25
25
  import { ensureMinimumSdkVersionIsInstalled } from './utils/sdk-version';
26
26
  import { traceStep, withTelemetry } from '../telemetry';
27
- import { URL } from 'url';
28
27
  import { checkIfMoreSuitableWizardExistsAndAskForRedirect } from './utils/other-wizards';
29
28
  import { configureAngularSourcemapGenerationFlow } from './tools/angular';
30
29
  import { detectUsedTool, SupportedTools } from './utils/detect-tool';
31
30
  import { configureNextJsSourceMapsUpload } from './tools/nextjs';
32
31
  import { configureRemixSourceMapsUpload } from './tools/remix';
33
32
  import { detectPackageManger } from '../utils/package-manager';
33
+ import { getIssueStreamUrl } from '../utils/url';
34
34
 
35
35
  export async function runSourcemapsWizard(
36
36
  options: WizardOptions,
@@ -334,12 +334,7 @@ function printOutro(url: string, orgSlug: string, projectId: string) {
334
334
  const packageManager = detectPackageManger();
335
335
  const buildCommand = packageManager?.buildCommand ?? 'npm run build';
336
336
 
337
- const urlObject = new URL(url);
338
- urlObject.host = `${orgSlug}.${urlObject.host}`;
339
- urlObject.pathname = '/issues/';
340
- urlObject.searchParams.set('project', projectId);
341
-
342
- const issueStreamUrl = urlObject.toString();
337
+ const issueStreamUrl = getIssueStreamUrl({ url, orgSlug, projectId });
343
338
 
344
339
  const arrow = isUnicodeSupported() ? '→' : '->';
345
340
 
@@ -1001,7 +1001,6 @@ async function askForProjectSelection(
1001
1001
  );
1002
1002
 
1003
1003
  Sentry.setTag('project', selection.slug);
1004
- Sentry.setTag('project-platform', selection.platform);
1005
1004
  Sentry.setUser({ id: selection.organization.slug });
1006
1005
 
1007
1006
  return selection;
@@ -1,15 +1,18 @@
1
1
  export interface SentryProjectData {
2
2
  id: string;
3
3
  slug: string;
4
- name: string;
5
- platform: string;
4
+ status: string;
6
5
  organization: {
6
+ id: string;
7
+ name: string;
7
8
  slug: string;
8
- links: {
9
- organizationUrl: string;
9
+ region: string;
10
+ status: {
11
+ id: string;
12
+ name: string;
10
13
  };
11
14
  };
12
- keys: [{ dsn: { public: string } }];
15
+ keys: [{ dsn: { public: string }; isActive: boolean }];
13
16
  }
14
17
 
15
18
  export type WizardOptions = {
@@ -0,0 +1,23 @@
1
+ import { URL } from 'url';
2
+
3
+ /**
4
+ * Returns the url to the Sentry project stream.
5
+ *
6
+ * Example: https://org-slug.sentry.io/issues/?project=1234567
7
+ */
8
+ export function getIssueStreamUrl({
9
+ url,
10
+ orgSlug,
11
+ projectId,
12
+ }: {
13
+ url: string;
14
+ orgSlug: string;
15
+ projectId: string;
16
+ }): string {
17
+ const urlObject = new URL(url);
18
+ urlObject.host = `${orgSlug}.${urlObject.host}`;
19
+ urlObject.pathname = '/issues/';
20
+ urlObject.searchParams.set('project', projectId);
21
+
22
+ return urlObject.toString();
23
+ }