@sentry/wizard 3.0.0 → 3.1.0-beta.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 +4 -0
- package/bin.ts +9 -2
- package/dist/bin.js +10 -2
- package/dist/bin.js.map +1 -1
- package/dist/lib/Helper/MergeConfig.js +1 -0
- package/dist/lib/Helper/MergeConfig.js.map +1 -1
- package/dist/lib/Steps/BaseStep.js.map +1 -1
- package/dist/lib/Steps/Initial.js +1 -0
- package/dist/lib/Steps/Initial.js.map +1 -1
- package/dist/lib/Steps/Integrations/BaseIntegration.js +2 -0
- package/dist/lib/Steps/Integrations/BaseIntegration.js.map +1 -1
- package/dist/lib/Steps/Integrations/Cordova.js +1 -0
- package/dist/lib/Steps/Integrations/Cordova.js.map +1 -1
- package/dist/lib/Steps/Integrations/Electron.js +2 -0
- package/dist/lib/Steps/Integrations/Electron.js.map +1 -1
- package/dist/lib/Steps/Integrations/MobileProject.js +1 -0
- package/dist/lib/Steps/Integrations/MobileProject.js.map +1 -1
- package/dist/lib/Steps/Integrations/NextJs.js +1 -0
- package/dist/lib/Steps/Integrations/NextJs.js.map +1 -1
- package/dist/lib/Steps/Integrations/ReactNative.js +2 -1
- package/dist/lib/Steps/Integrations/ReactNative.js.map +1 -1
- package/dist/lib/Steps/Result.js +1 -0
- package/dist/lib/Steps/Result.js.map +1 -1
- package/dist/lib/Steps/WaitForSentry.js +42 -47
- package/dist/lib/Steps/WaitForSentry.js.map +1 -1
- package/dist/lib/Steps/Welcome.js +1 -0
- package/dist/lib/Steps/Welcome.js.map +1 -1
- package/dist/src/clack-utils.d.ts +40 -0
- package/dist/src/clack-utils.js +329 -0
- package/dist/src/clack-utils.js.map +1 -0
- package/dist/src/nextjs-wizard.d.ts +5 -0
- package/dist/src/nextjs-wizard.js +346 -0
- package/dist/src/nextjs-wizard.js.map +1 -0
- package/lib/Helper/MergeConfig.ts +1 -0
- package/lib/Steps/BaseStep.ts +1 -1
- package/lib/Steps/Initial.ts +1 -0
- package/lib/Steps/Integrations/BaseIntegration.ts +2 -0
- package/lib/Steps/Integrations/Cordova.ts +2 -1
- package/lib/Steps/Integrations/Electron.ts +3 -1
- package/lib/Steps/Integrations/MobileProject.ts +1 -0
- package/lib/Steps/Integrations/NextJs.ts +1 -0
- package/lib/Steps/Integrations/ReactNative.ts +3 -2
- package/lib/Steps/Result.ts +1 -0
- package/lib/Steps/WaitForSentry.ts +1 -1
- package/lib/Steps/Welcome.ts +2 -1
- package/package.json +10 -3
- package/src/clack-utils.ts +346 -0
- package/src/nextjs-wizard.ts +619 -0
|
@@ -0,0 +1,619 @@
|
|
|
1
|
+
/* eslint-disable max-lines */
|
|
2
|
+
import * as clack from '@clack/prompts';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import * as fs from 'fs';
|
|
5
|
+
import { builders, generateCode, parseModule } from 'magicast';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
abort,
|
|
10
|
+
abortIfCancelled,
|
|
11
|
+
addSentryCliRc,
|
|
12
|
+
askForSelfHosted,
|
|
13
|
+
askForWizardLogin,
|
|
14
|
+
confirmContinueEvenThoughNoGitRepo,
|
|
15
|
+
installPackage,
|
|
16
|
+
printWelcome,
|
|
17
|
+
SentryProjectData,
|
|
18
|
+
} from './clack-utils';
|
|
19
|
+
|
|
20
|
+
interface NextjsWizardOptions {
|
|
21
|
+
promoCode?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// eslint-disable-next-line complexity
|
|
25
|
+
export async function runNextjsWizard(
|
|
26
|
+
options: NextjsWizardOptions,
|
|
27
|
+
): Promise<void> {
|
|
28
|
+
printWelcome({
|
|
29
|
+
wizardName: 'Sentry Next.js Wizard',
|
|
30
|
+
promoCode: options.promoCode,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
await confirmContinueEvenThoughNoGitRepo();
|
|
34
|
+
|
|
35
|
+
const packageJsonFileContents = await fs.promises
|
|
36
|
+
.readFile(path.join(process.cwd(), 'package.json'), 'utf8')
|
|
37
|
+
.catch(() => {
|
|
38
|
+
clack.log.error(
|
|
39
|
+
'Could not find package.json. Make sure to run the wizard in the root of your Next.js app!',
|
|
40
|
+
);
|
|
41
|
+
abort();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
let packageJson:
|
|
45
|
+
| { dependencies?: { ['@sentry/nextjs']: string; ['next']: string } }
|
|
46
|
+
| undefined = undefined;
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
50
|
+
packageJson = JSON.parse(packageJsonFileContents);
|
|
51
|
+
} catch (e) {
|
|
52
|
+
clack.log.error(
|
|
53
|
+
'Unable to parse your package.json. Make sure it has a valid format!',
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
abort();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!packageJson?.dependencies?.['next']) {
|
|
60
|
+
const continueWithoutNext = await clack.confirm({
|
|
61
|
+
message:
|
|
62
|
+
'Next.js does not seem to be installed. Do you still want to continue?',
|
|
63
|
+
initialValue: false,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
abortIfCancelled(continueWithoutNext);
|
|
67
|
+
|
|
68
|
+
if (!continueWithoutNext) {
|
|
69
|
+
abort();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const { url: sentryUrl, selfHosted } = await askForSelfHosted();
|
|
74
|
+
|
|
75
|
+
const { projects, apiKeys } = await askForWizardLogin({
|
|
76
|
+
promoCode: options.promoCode,
|
|
77
|
+
url: sentryUrl,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const selectedProject: SentryProjectData | symbol = await clack.select({
|
|
81
|
+
message: 'Select your Sentry project.',
|
|
82
|
+
options: projects.map(project => {
|
|
83
|
+
return {
|
|
84
|
+
value: project,
|
|
85
|
+
label: `${project.organization.slug}/${project.slug}`,
|
|
86
|
+
};
|
|
87
|
+
}),
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
abortIfCancelled(selectedProject);
|
|
91
|
+
|
|
92
|
+
await installPackage({
|
|
93
|
+
packageName: '@sentry/nextjs',
|
|
94
|
+
alreadyInstalled: !!packageJson?.dependencies?.['@sentry/nextjs'],
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
let isUsingTypescript = false;
|
|
98
|
+
try {
|
|
99
|
+
isUsingTypescript = fs.existsSync(
|
|
100
|
+
path.join(process.cwd(), 'tsconfig.json'),
|
|
101
|
+
);
|
|
102
|
+
} catch (e) {
|
|
103
|
+
// noop - Default to assuming user is not using typescript
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const configVariants = ['server', 'client', 'edge'] as const;
|
|
107
|
+
|
|
108
|
+
for (const configVariant of configVariants) {
|
|
109
|
+
const jsConfig = `sentry.${configVariant}.config.js`;
|
|
110
|
+
const tsConfig = `sentry.${configVariant}.config.ts`;
|
|
111
|
+
|
|
112
|
+
const jsConfigExists = fs.existsSync(path.join(process.cwd(), jsConfig));
|
|
113
|
+
const tsConfigExists = fs.existsSync(path.join(process.cwd(), tsConfig));
|
|
114
|
+
|
|
115
|
+
let shouldWriteFile = true;
|
|
116
|
+
|
|
117
|
+
if (jsConfigExists || tsConfigExists) {
|
|
118
|
+
const existingConfigs = [];
|
|
119
|
+
|
|
120
|
+
if (jsConfigExists) {
|
|
121
|
+
existingConfigs.push(jsConfig);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (tsConfigExists) {
|
|
125
|
+
existingConfigs.push(tsConfig);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const overwriteExistingConfigs = await clack.confirm({
|
|
129
|
+
message: `Found existing Sentry ${configVariant} config (${existingConfigs.join(
|
|
130
|
+
', ',
|
|
131
|
+
)}). Overwrite ${existingConfigs.length > 1 ? 'them' : 'it'}?`,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
abortIfCancelled(overwriteExistingConfigs);
|
|
135
|
+
|
|
136
|
+
shouldWriteFile = overwriteExistingConfigs;
|
|
137
|
+
|
|
138
|
+
if (overwriteExistingConfigs) {
|
|
139
|
+
if (jsConfigExists) {
|
|
140
|
+
fs.unlinkSync(path.join(process.cwd(), jsConfig));
|
|
141
|
+
clack.log.warn(`Removed existing ${chalk.bold(jsConfig)}.`);
|
|
142
|
+
}
|
|
143
|
+
if (tsConfigExists) {
|
|
144
|
+
fs.unlinkSync(path.join(process.cwd(), tsConfig));
|
|
145
|
+
clack.log.warn(`Removed existing ${chalk.bold(tsConfig)}.`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (shouldWriteFile) {
|
|
151
|
+
await fs.promises.writeFile(
|
|
152
|
+
path.join(process.cwd(), isUsingTypescript ? tsConfig : jsConfig),
|
|
153
|
+
getSentryConfigContents(
|
|
154
|
+
selectedProject.keys[0].dsn.public,
|
|
155
|
+
configVariant,
|
|
156
|
+
),
|
|
157
|
+
{ encoding: 'utf8', flag: 'w' },
|
|
158
|
+
);
|
|
159
|
+
clack.log.success(
|
|
160
|
+
`Created fresh ${chalk.bold(isUsingTypescript ? tsConfig : jsConfig)}.`,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const webpackOptionsTemplate = `{
|
|
166
|
+
// For all available options, see:
|
|
167
|
+
// https://github.com/getsentry/sentry-webpack-plugin#options
|
|
168
|
+
|
|
169
|
+
// Suppresses source map uploading logs during build
|
|
170
|
+
silent: true,
|
|
171
|
+
|
|
172
|
+
org: "${selectedProject.organization.slug}",
|
|
173
|
+
project: "${selectedProject.slug}",
|
|
174
|
+
}`;
|
|
175
|
+
|
|
176
|
+
const sentryBuildOptionsTemplate = `{
|
|
177
|
+
// For all available options, see:
|
|
178
|
+
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
|
|
179
|
+
|
|
180
|
+
// Upload a larger set of source maps for prettier stack traces (increases build time)
|
|
181
|
+
widenClientFileUpload: true,
|
|
182
|
+
|
|
183
|
+
// Transpiles SDK to be compatible with IE11 (increases bundle size)
|
|
184
|
+
transpileClientSDK: true,
|
|
185
|
+
|
|
186
|
+
// Routes browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers (increases server load)
|
|
187
|
+
tunnelRoute: "/monitoring",
|
|
188
|
+
|
|
189
|
+
// Hides source maps from generated client bundles
|
|
190
|
+
hideSourceMaps: true,
|
|
191
|
+
|
|
192
|
+
// Automatically tree-shake Sentry logger statements to reduce bundle size
|
|
193
|
+
disableLogger: true,
|
|
194
|
+
}`;
|
|
195
|
+
|
|
196
|
+
const newNextConfigTemplate = `const { withSentryConfig } = require("@sentry/nextjs");
|
|
197
|
+
|
|
198
|
+
/** @type {import('next').NextConfig} */
|
|
199
|
+
const nextConfig = {};
|
|
200
|
+
|
|
201
|
+
module.exports = withSentryConfig(
|
|
202
|
+
nextConfig,
|
|
203
|
+
${webpackOptionsTemplate},
|
|
204
|
+
${sentryBuildOptionsTemplate}
|
|
205
|
+
);
|
|
206
|
+
`;
|
|
207
|
+
|
|
208
|
+
const nextConfigJs = 'next.config.js';
|
|
209
|
+
const nextConfigMjs = 'next.config.mjs';
|
|
210
|
+
|
|
211
|
+
const nextConfigJsExists = fs.existsSync(
|
|
212
|
+
path.join(process.cwd(), nextConfigJs),
|
|
213
|
+
);
|
|
214
|
+
const nextConfigMjsExists = fs.existsSync(
|
|
215
|
+
path.join(process.cwd(), nextConfigMjs),
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
if (!nextConfigJsExists && !nextConfigMjsExists) {
|
|
219
|
+
await fs.promises.writeFile(
|
|
220
|
+
path.join(process.cwd(), nextConfigJs),
|
|
221
|
+
newNextConfigTemplate,
|
|
222
|
+
{ encoding: 'utf8', flag: 'w' },
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
clack.log.success(
|
|
226
|
+
`Created ${chalk.bold('next.config.js')} with Sentry configuration.`,
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (nextConfigJsExists) {
|
|
231
|
+
const nextConfgiJsContent = fs.readFileSync(
|
|
232
|
+
path.join(process.cwd(), nextConfigJs),
|
|
233
|
+
'utf8',
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
const probablyIncludesSdk =
|
|
237
|
+
nextConfgiJsContent.includes('@sentry/nextjs') &&
|
|
238
|
+
nextConfgiJsContent.includes('withSentryConfig');
|
|
239
|
+
|
|
240
|
+
let shouldInject = true;
|
|
241
|
+
|
|
242
|
+
if (probablyIncludesSdk) {
|
|
243
|
+
const injectAnyhow = await clack.confirm({
|
|
244
|
+
message: `${chalk.bold(
|
|
245
|
+
nextConfigMjs,
|
|
246
|
+
)} already contains Sentry SDK configuration. Should the wizard modify it anyways?`,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
abortIfCancelled(injectAnyhow);
|
|
250
|
+
|
|
251
|
+
shouldInject = injectAnyhow;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (shouldInject) {
|
|
255
|
+
const cjsAppendix = `
|
|
256
|
+
|
|
257
|
+
// Inected Content via Sentry Wizard Below
|
|
258
|
+
|
|
259
|
+
const { withSentryConfig } = require("@sentry/nextjs");
|
|
260
|
+
|
|
261
|
+
module.exports = withSentryConfig(
|
|
262
|
+
module.exports,
|
|
263
|
+
${webpackOptionsTemplate},
|
|
264
|
+
${sentryBuildOptionsTemplate}
|
|
265
|
+
);
|
|
266
|
+
`;
|
|
267
|
+
fs.appendFileSync(
|
|
268
|
+
path.join(process.cwd(), nextConfigJs),
|
|
269
|
+
cjsAppendix,
|
|
270
|
+
'utf8',
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
clack.log.success(
|
|
274
|
+
`Added Sentry configuration to ${chalk.bold(nextConfigJs)}. ${chalk.dim(
|
|
275
|
+
'(you probably want to clean this up a bit!)',
|
|
276
|
+
)}`,
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (nextConfigMjsExists) {
|
|
282
|
+
const nextConfgiMjsContent = fs.readFileSync(
|
|
283
|
+
path.join(process.cwd(), nextConfigMjs),
|
|
284
|
+
'utf8',
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
const probablyIncludesSdk =
|
|
288
|
+
nextConfgiMjsContent.includes('@sentry/nextjs') &&
|
|
289
|
+
nextConfgiMjsContent.includes('withSentryConfig');
|
|
290
|
+
|
|
291
|
+
let shouldInject = true;
|
|
292
|
+
|
|
293
|
+
if (probablyIncludesSdk) {
|
|
294
|
+
const injectAnyhow = await clack.confirm({
|
|
295
|
+
message: `${chalk.bold(
|
|
296
|
+
nextConfigMjs,
|
|
297
|
+
)} already contains Sentry SDK configuration. Should the wizard modify it anyways?`,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
abortIfCancelled(injectAnyhow);
|
|
301
|
+
shouldInject = injectAnyhow;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
try {
|
|
305
|
+
if (shouldInject) {
|
|
306
|
+
const mod = parseModule(nextConfgiMjsContent);
|
|
307
|
+
mod.imports.$add({
|
|
308
|
+
from: '@sentry/nextjs',
|
|
309
|
+
imported: 'withSentryConfig',
|
|
310
|
+
local: 'withSentryConfig',
|
|
311
|
+
});
|
|
312
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
|
|
313
|
+
const expressionToWrap = generateCode(mod.exports.default.$ast).code;
|
|
314
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
315
|
+
mod.exports.default = builders.raw(`withSentryConfig(
|
|
316
|
+
${expressionToWrap},
|
|
317
|
+
${webpackOptionsTemplate},
|
|
318
|
+
${sentryBuildOptionsTemplate}
|
|
319
|
+
)`);
|
|
320
|
+
const newCode = mod.generate().code;
|
|
321
|
+
|
|
322
|
+
await fs.promises.writeFile(
|
|
323
|
+
path.join(process.cwd(), nextConfigMjs),
|
|
324
|
+
newCode,
|
|
325
|
+
{
|
|
326
|
+
encoding: 'utf8',
|
|
327
|
+
flag: 'w',
|
|
328
|
+
},
|
|
329
|
+
);
|
|
330
|
+
clack.log.success(
|
|
331
|
+
`Added Sentry configuration to ${chalk.bold(
|
|
332
|
+
nextConfigMjs,
|
|
333
|
+
)}. ${chalk.dim('(you probably want to clean this up a bit!)')}`,
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
} catch (e) {
|
|
337
|
+
clack.log.warn(
|
|
338
|
+
chalk.yellow(
|
|
339
|
+
`Something went wrong writing to ${chalk.bold(nextConfigMjs)}`,
|
|
340
|
+
),
|
|
341
|
+
);
|
|
342
|
+
clack.log.info(
|
|
343
|
+
`Please put the following code snippet into ${chalk.bold(
|
|
344
|
+
nextConfigMjs,
|
|
345
|
+
)}: ${chalk.dim('You probably have to clean it up a bit.')}\n`,
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
// eslint-disable-next-line no-console
|
|
349
|
+
console.log(`\n
|
|
350
|
+
// next.config.mjs
|
|
351
|
+
import { withSentryConfig } from "@sentry/nextjs";
|
|
352
|
+
|
|
353
|
+
export default withSentryConfig(
|
|
354
|
+
yourNextConfig,
|
|
355
|
+
${webpackOptionsTemplate},
|
|
356
|
+
${sentryBuildOptionsTemplate}
|
|
357
|
+
);\n`);
|
|
358
|
+
|
|
359
|
+
const shouldContinue = await clack.confirm({
|
|
360
|
+
message: `Are you done putting the snippet above into ${chalk.bold(
|
|
361
|
+
nextConfigMjs,
|
|
362
|
+
)}?`,
|
|
363
|
+
active: 'Yes',
|
|
364
|
+
inactive: 'No, get me out of here',
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
abortIfCancelled(shouldContinue);
|
|
368
|
+
if (!shouldContinue) {
|
|
369
|
+
abort();
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const maybePagesDirPath = path.join(process.cwd(), 'pages');
|
|
375
|
+
const maybeSrcPagesDirPath = path.join(process.cwd(), 'src', 'pages');
|
|
376
|
+
|
|
377
|
+
let pagesLocation =
|
|
378
|
+
fs.existsSync(maybePagesDirPath) &&
|
|
379
|
+
fs.lstatSync(maybePagesDirPath).isDirectory()
|
|
380
|
+
? ['pages']
|
|
381
|
+
: fs.existsSync(maybeSrcPagesDirPath) &&
|
|
382
|
+
fs.lstatSync(maybeSrcPagesDirPath).isDirectory()
|
|
383
|
+
? ['src', 'pages']
|
|
384
|
+
: undefined;
|
|
385
|
+
|
|
386
|
+
if (!pagesLocation) {
|
|
387
|
+
pagesLocation = ['pages'];
|
|
388
|
+
fs.mkdirSync(path.join(process.cwd(), ...pagesLocation), {
|
|
389
|
+
recursive: true,
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (pagesLocation) {
|
|
394
|
+
const examplePageContents = createExamplePage({
|
|
395
|
+
selfHosted,
|
|
396
|
+
orgSlug: selectedProject.organization.slug,
|
|
397
|
+
projectId: selectedProject.id,
|
|
398
|
+
url: sentryUrl,
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
await fs.promises.writeFile(
|
|
402
|
+
path.join(process.cwd(), ...pagesLocation, 'sentry-example-page.js'),
|
|
403
|
+
examplePageContents,
|
|
404
|
+
{ encoding: 'utf8', flag: 'w' },
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
clack.log.success(
|
|
408
|
+
`Created ${chalk.bold(
|
|
409
|
+
path.join(...pagesLocation, 'sentry-example-page.js'),
|
|
410
|
+
)}.`,
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
fs.mkdirSync(path.join(process.cwd(), ...pagesLocation, 'api'), {
|
|
414
|
+
recursive: true,
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
await fs.promises.writeFile(
|
|
418
|
+
path.join(
|
|
419
|
+
process.cwd(),
|
|
420
|
+
...pagesLocation,
|
|
421
|
+
'api',
|
|
422
|
+
'sentry-example-api.js',
|
|
423
|
+
),
|
|
424
|
+
exampleApiRoute,
|
|
425
|
+
{ encoding: 'utf8', flag: 'w' },
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
clack.log.success(
|
|
429
|
+
`Created ${chalk.bold(
|
|
430
|
+
path.join(...pagesLocation, 'api', 'sentry-example-api.js'),
|
|
431
|
+
)}.`,
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
await addSentryCliRc(apiKeys.token);
|
|
436
|
+
|
|
437
|
+
const mightBeUsingVercel = fs.existsSync(
|
|
438
|
+
path.join(process.cwd(), 'vercel.json'),
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
clack.outro(
|
|
442
|
+
`${chalk.green('Everything is set up!')}
|
|
443
|
+
|
|
444
|
+
${chalk.cyan(
|
|
445
|
+
'You can validate your setup by starting your dev environment (`next dev`) and visiting "/sentry-example-page".',
|
|
446
|
+
)}
|
|
447
|
+
${
|
|
448
|
+
mightBeUsingVercel
|
|
449
|
+
? `
|
|
450
|
+
▲ It seems like you're using Vercel. We recommend using the Sentry Vercel integration: https://vercel.com/integrations/sentry
|
|
451
|
+
`
|
|
452
|
+
: ''
|
|
453
|
+
}
|
|
454
|
+
${chalk.dim(
|
|
455
|
+
'If you encounter any issues, let us know here: https://github.com/getsentry/sentry-javascript/issues',
|
|
456
|
+
)}`,
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function getSentryConfigContents(
|
|
461
|
+
dsn: string,
|
|
462
|
+
config: 'server' | 'client' | 'edge',
|
|
463
|
+
): string {
|
|
464
|
+
let primer;
|
|
465
|
+
if (config === 'server') {
|
|
466
|
+
primer = `// This file configures the initialization of Sentry on the server.
|
|
467
|
+
// The config you add here will be used whenever the server handles a request.
|
|
468
|
+
// https://docs.sentry.io/platforms/javascript/guides/nextjs/`;
|
|
469
|
+
} else if (config === 'client') {
|
|
470
|
+
primer = `// This file configures the initialization of Sentry on the client.
|
|
471
|
+
// The config you add here will be used whenever a users loads a page in their browser.
|
|
472
|
+
// https://docs.sentry.io/platforms/javascript/guides/nextjs/`;
|
|
473
|
+
} else if (config === 'edge') {
|
|
474
|
+
primer = `// This file configures the initialization of Sentry for edge features (middleware, edge routes, and so on).
|
|
475
|
+
// The config you add here will be used whenever one of the edge features is loaded.
|
|
476
|
+
// Note that this config is unrelated to the Verel Edge Runtime and is also required when running locally.
|
|
477
|
+
// https://docs.sentry.io/platforms/javascript/guides/nextjs/`;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
let additionalOptions = '';
|
|
481
|
+
if (config === 'client') {
|
|
482
|
+
additionalOptions = `
|
|
483
|
+
|
|
484
|
+
replaysOnErrorSampleRate: 1.0,
|
|
485
|
+
|
|
486
|
+
// This sets the sample rate to be 10%. You may want this to be 100% while
|
|
487
|
+
// in development and sample at a lower rate in production
|
|
488
|
+
replaysSessionSampleRate: 0.1,
|
|
489
|
+
|
|
490
|
+
// You can remove this option if you're not planning to use the Sentry Session Replay feature:
|
|
491
|
+
integrations: [
|
|
492
|
+
new Sentry.Replay({
|
|
493
|
+
// Additional Replay configuration goes in here, for example:
|
|
494
|
+
maskAllText: true,
|
|
495
|
+
blockAllMedia: true,
|
|
496
|
+
}),
|
|
497
|
+
],`;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
501
|
+
return `${primer}
|
|
502
|
+
|
|
503
|
+
import * as Sentry from "@sentry/nextjs";
|
|
504
|
+
|
|
505
|
+
Sentry.init({
|
|
506
|
+
dsn: "${dsn}",
|
|
507
|
+
|
|
508
|
+
// Adjust this value in production, or use tracesSampler for greater control
|
|
509
|
+
tracesSampleRate: 1,
|
|
510
|
+
|
|
511
|
+
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
|
512
|
+
debug: false,${additionalOptions}
|
|
513
|
+
});
|
|
514
|
+
`;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function createExamplePage(options: {
|
|
518
|
+
selfHosted: boolean;
|
|
519
|
+
url: string;
|
|
520
|
+
orgSlug: string;
|
|
521
|
+
projectId: string;
|
|
522
|
+
}): string {
|
|
523
|
+
const issuesPageLink = options.selfHosted
|
|
524
|
+
? `${options.url}organizations/${options.orgSlug}/issues/?project=${options.projectId}`
|
|
525
|
+
: `https://${options.orgSlug}.sentry.io/issues/?project=${options.projectId}`;
|
|
526
|
+
|
|
527
|
+
return `import Head from "next/head";
|
|
528
|
+
import * as Sentry from "@sentry/nextjs";
|
|
529
|
+
|
|
530
|
+
export default function Home() {
|
|
531
|
+
return (
|
|
532
|
+
<div>
|
|
533
|
+
<Head>
|
|
534
|
+
<title>Sentry Onboarding</title>
|
|
535
|
+
<meta name="description" content="Test Sentry for your Next.js app!" />
|
|
536
|
+
</Head>
|
|
537
|
+
|
|
538
|
+
<main
|
|
539
|
+
style={{
|
|
540
|
+
minHeight: "100vh",
|
|
541
|
+
display: "flex",
|
|
542
|
+
flexDirection: "column",
|
|
543
|
+
justifyContent: "center",
|
|
544
|
+
alignItems: "center",
|
|
545
|
+
}}
|
|
546
|
+
>
|
|
547
|
+
<h1 style={{ fontSize: "4rem", margin: "14px 0" }}>
|
|
548
|
+
<svg
|
|
549
|
+
style={{
|
|
550
|
+
height: "1em",
|
|
551
|
+
}}
|
|
552
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
553
|
+
viewBox="0 0 200 44"
|
|
554
|
+
>
|
|
555
|
+
<path
|
|
556
|
+
fill="currentColor"
|
|
557
|
+
d="M124.32,28.28,109.56,9.22h-3.68V34.77h3.73V15.19l15.18,19.58h3.26V9.22h-3.73ZM87.15,23.54h13.23V20.22H87.14V12.53h14.93V9.21H83.34V34.77h18.92V31.45H87.14ZM71.59,20.3h0C66.44,19.06,65,18.08,65,15.7c0-2.14,1.89-3.59,4.71-3.59a12.06,12.06,0,0,1,7.07,2.55l2-2.83a14.1,14.1,0,0,0-9-3c-5.06,0-8.59,3-8.59,7.27,0,4.6,3,6.19,8.46,7.52C74.51,24.74,76,25.78,76,28.11s-2,3.77-5.09,3.77a12.34,12.34,0,0,1-8.3-3.26l-2.25,2.69a15.94,15.94,0,0,0,10.42,3.85c5.48,0,9-2.95,9-7.51C79.75,23.79,77.47,21.72,71.59,20.3ZM195.7,9.22l-7.69,12-7.64-12h-4.46L186,24.67V34.78h3.84V24.55L200,9.22Zm-64.63,3.46h8.37v22.1h3.84V12.68h8.37V9.22H131.08ZM169.41,24.8c3.86-1.07,6-3.77,6-7.63,0-4.91-3.59-8-9.38-8H154.67V34.76h3.8V25.58h6.45l6.48,9.2h4.44l-7-9.82Zm-10.95-2.5V12.6h7.17c3.74,0,5.88,1.77,5.88,4.84s-2.29,4.86-5.84,4.86Z M29,2.26a4.67,4.67,0,0,0-8,0L14.42,13.53A32.21,32.21,0,0,1,32.17,40.19H27.55A27.68,27.68,0,0,0,12.09,17.47L6,28a15.92,15.92,0,0,1,9.23,12.17H4.62A.76.76,0,0,1,4,39.06l2.94-5a10.74,10.74,0,0,0-3.36-1.9l-2.91,5a4.54,4.54,0,0,0,1.69,6.24A4.66,4.66,0,0,0,4.62,44H19.15a19.4,19.4,0,0,0-8-17.31l2.31-4A23.87,23.87,0,0,1,23.76,44H36.07a35.88,35.88,0,0,0-16.41-31.8l4.67-8a.77.77,0,0,1,1.05-.27c.53.29,20.29,34.77,20.66,35.17a.76.76,0,0,1-.68,1.13H40.6q.09,1.91,0,3.81h4.78A4.59,4.59,0,0,0,50,39.43a4.49,4.49,0,0,0-.62-2.28Z"
|
|
558
|
+
></path>
|
|
559
|
+
</svg>
|
|
560
|
+
</h1>
|
|
561
|
+
|
|
562
|
+
<p>Get started by sending us a sample error:</p>
|
|
563
|
+
<button
|
|
564
|
+
type="button"
|
|
565
|
+
style={{
|
|
566
|
+
padding: "12px",
|
|
567
|
+
cursor: "pointer",
|
|
568
|
+
backgroundColor: "#AD6CAA",
|
|
569
|
+
borderRadius: "4px",
|
|
570
|
+
border: "none",
|
|
571
|
+
color: "white",
|
|
572
|
+
fontSize: "14px",
|
|
573
|
+
margin: "18px",
|
|
574
|
+
}}
|
|
575
|
+
onClick={async () => {
|
|
576
|
+
const transaction = Sentry.startTransaction({
|
|
577
|
+
name: "Example Frontend Transaction",
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
Sentry.configureScope((scope) => {
|
|
581
|
+
scope.setSpan(transaction);
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
try {
|
|
585
|
+
const res = await fetch("/api/sentry-example-api");
|
|
586
|
+
if (!res.ok) {
|
|
587
|
+
throw new Error("Sentry Example Frontend Error");
|
|
588
|
+
}
|
|
589
|
+
} finally {
|
|
590
|
+
transaction.finish();
|
|
591
|
+
}
|
|
592
|
+
}}
|
|
593
|
+
>
|
|
594
|
+
Throw error!
|
|
595
|
+
</button>
|
|
596
|
+
|
|
597
|
+
<p>
|
|
598
|
+
Next, look for the error on the{" "}
|
|
599
|
+
<a href="${issuesPageLink}">Issues Page</a>.
|
|
600
|
+
</p>
|
|
601
|
+
<p style={{ marginTop: "24px" }}>
|
|
602
|
+
For more information, see{" "}
|
|
603
|
+
<a href="https://docs.sentry.io/platforms/javascript/guides/nextjs/">
|
|
604
|
+
https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
|
605
|
+
</a>
|
|
606
|
+
</p>
|
|
607
|
+
</main>
|
|
608
|
+
</div>
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
`;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const exampleApiRoute = `// A faulty API route to test Sentry's error monitoring
|
|
615
|
+
export default function handler(_req, res) {
|
|
616
|
+
throw new Error("Sentry Example API Route Error");
|
|
617
|
+
res.status(200).json({ name: "John Doe" });
|
|
618
|
+
}
|
|
619
|
+
`;
|