@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.
- package/CHANGELOG.md +27 -10
- package/dist/package.json +1 -1
- package/dist/src/nextjs/nextjs-wizard.js +78 -2
- package/dist/src/nextjs/nextjs-wizard.js.map +1 -1
- package/dist/src/nextjs/templates.d.ts +3 -0
- package/dist/src/nextjs/templates.js +17 -1
- package/dist/src/nextjs/templates.js.map +1 -1
- package/dist/src/react-native/react-native-wizard.js +8 -6
- package/dist/src/react-native/react-native-wizard.js.map +1 -1
- package/dist/src/remix/remix-wizard.js +1 -1
- package/dist/src/remix/remix-wizard.js.map +1 -1
- package/dist/src/sourcemaps/sourcemaps-wizard.js +2 -6
- package/dist/src/sourcemaps/sourcemaps-wizard.js.map +1 -1
- package/dist/src/utils/clack-utils.js +0 -1
- package/dist/src/utils/clack-utils.js.map +1 -1
- package/dist/src/utils/types.d.ts +8 -4
- package/dist/src/utils/types.js.map +1 -1
- package/dist/src/utils/url.d.ts +10 -0
- package/dist/src/utils/url.js +19 -0
- package/dist/src/utils/url.js.map +1 -0
- package/package.json +1 -1
- package/src/nextjs/nextjs-wizard.ts +106 -0
- package/src/nextjs/templates.ts +57 -0
- package/src/react-native/react-native-wizard.ts +12 -5
- package/src/remix/remix-wizard.ts +1 -1
- package/src/sourcemaps/sourcemaps-wizard.ts +2 -7
- package/src/utils/clack-utils.ts +0 -1
- package/src/utils/types.ts +8 -5
- package/src/utils/url.ts +23 -0
|
@@ -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
|
);
|
package/src/nextjs/templates.ts
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
211
|
-
|
|
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(
|
|
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.
|
|
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
|
|
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
|
|
package/src/utils/clack-utils.ts
CHANGED
package/src/utils/types.ts
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
export interface SentryProjectData {
|
|
2
2
|
id: string;
|
|
3
3
|
slug: string;
|
|
4
|
-
|
|
5
|
-
platform: string;
|
|
4
|
+
status: string;
|
|
6
5
|
organization: {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
7
8
|
slug: string;
|
|
8
|
-
|
|
9
|
-
|
|
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 = {
|
package/src/utils/url.ts
ADDED
|
@@ -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
|
+
}
|