@sentry/wizard 1.2.11 → 1.2.15
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 +98 -80
- package/README.md +26 -11
- package/dist/NextJs/configs/_error.js +61 -0
- package/dist/NextJs/configs/next.config.js +2 -2
- package/dist/lib/Helper/SentryCli.d.ts +4 -0
- package/dist/lib/Helper/SentryCli.js +14 -0
- package/dist/lib/Helper/SentryCli.js.map +1 -1
- package/dist/lib/Steps/Integrations/BaseIntegration.d.ts +1 -1
- package/dist/lib/Steps/Integrations/BaseIntegration.js +3 -2
- package/dist/lib/Steps/Integrations/BaseIntegration.js.map +1 -1
- package/dist/lib/Steps/Integrations/NextJs.d.ts +5 -2
- package/dist/lib/Steps/Integrations/NextJs.js +187 -67
- package/dist/lib/Steps/Integrations/NextJs.js.map +1 -1
- package/lib/Helper/SentryCli.ts +17 -0
- package/lib/Steps/Integrations/BaseIntegration.ts +5 -2
- package/lib/Steps/Integrations/NextJs.ts +195 -71
- package/package.json +1 -1
- package/scripts/NextJs/configs/_error.js +61 -0
- package/scripts/NextJs/configs/next.config.js +2 -2
|
@@ -1,18 +1,30 @@
|
|
|
1
|
+
/* eslint-disable max-lines */
|
|
1
2
|
import * as fs from 'fs';
|
|
2
3
|
import { Answers, prompt } from 'inquirer';
|
|
3
4
|
import * as _ from 'lodash';
|
|
4
5
|
import * as path from 'path';
|
|
5
|
-
import {
|
|
6
|
+
import { satisfies, subset, valid, validRange } from 'semver';
|
|
6
7
|
|
|
7
8
|
import { Args } from '../../Constants';
|
|
8
9
|
import { debug, green, l, nl, red } from '../../Helper/Logging';
|
|
9
|
-
import { SentryCli } from '../../Helper/SentryCli';
|
|
10
|
+
import { SentryCli, SentryCliProps } from '../../Helper/SentryCli';
|
|
10
11
|
import { BaseIntegration } from './BaseIntegration';
|
|
11
12
|
|
|
12
|
-
const
|
|
13
|
+
const COMPATIBLE_NEXTJS_VERSIONS = '>=10.0.8 <12.0.0';
|
|
13
14
|
const PROPERTIES_FILENAME = 'sentry.properties';
|
|
15
|
+
const SENTRYCLIRC_FILENAME = '.sentryclirc';
|
|
16
|
+
const GITIGNORE_FILENAME = '.gitignore';
|
|
14
17
|
const CONFIG_DIR = 'configs/';
|
|
15
|
-
const
|
|
18
|
+
const MERGEABLE_CONFIG_INFIX = 'wizardcopy';
|
|
19
|
+
|
|
20
|
+
// for those files which can go in more than one place, the list of places they
|
|
21
|
+
// could go (the first one which works will be used)
|
|
22
|
+
const TEMPLATE_DESTINATIONS: { [key: string]: string[] } = {
|
|
23
|
+
'_error.js': ['pages', 'src/pages'],
|
|
24
|
+
'next.config.js': ['.'],
|
|
25
|
+
'sentry.server.config.js': ['.'],
|
|
26
|
+
'sentry.client.config.js': ['.'],
|
|
27
|
+
};
|
|
16
28
|
|
|
17
29
|
let appPackage: any = {};
|
|
18
30
|
|
|
@@ -35,12 +47,7 @@ export class NextJs extends BaseIntegration {
|
|
|
35
47
|
nl();
|
|
36
48
|
|
|
37
49
|
const sentryCliProps = this._sentryCli.convertAnswersToProperties(answers);
|
|
38
|
-
|
|
39
|
-
`./${PROPERTIES_FILENAME}`,
|
|
40
|
-
this._sentryCli.dumpProperties(sentryCliProps),
|
|
41
|
-
);
|
|
42
|
-
green(`Successfully created sentry.properties`);
|
|
43
|
-
nl();
|
|
50
|
+
await this._createSentryCliConfig(sentryCliProps);
|
|
44
51
|
|
|
45
52
|
const configDirectory = path.join(
|
|
46
53
|
__dirname,
|
|
@@ -55,7 +62,7 @@ export class NextJs extends BaseIntegration {
|
|
|
55
62
|
this._createNextConfig(configDirectory, dsn);
|
|
56
63
|
} else {
|
|
57
64
|
debug(
|
|
58
|
-
`Couldn't find ${configDirectory}, probably because you
|
|
65
|
+
`Couldn't find ${configDirectory}, probably because you ran this from inside of \`/lib\` rather than \`/dist\``,
|
|
59
66
|
);
|
|
60
67
|
nl();
|
|
61
68
|
}
|
|
@@ -76,7 +83,7 @@ export class NextJs extends BaseIntegration {
|
|
|
76
83
|
nl();
|
|
77
84
|
|
|
78
85
|
let userAnswers: Answers = { continue: true };
|
|
79
|
-
if (!this.
|
|
86
|
+
if (!this._checkUserNextVersion('next') && !this._argv.quiet) {
|
|
80
87
|
userAnswers = await prompt({
|
|
81
88
|
message:
|
|
82
89
|
'There were errors during your project checkup, do you still want to continue?',
|
|
@@ -97,10 +104,102 @@ export class NextJs extends BaseIntegration {
|
|
|
97
104
|
return this.shouldConfigure;
|
|
98
105
|
}
|
|
99
106
|
|
|
107
|
+
private async _createSentryCliConfig(
|
|
108
|
+
cliProps: SentryCliProps,
|
|
109
|
+
): Promise<void> {
|
|
110
|
+
const { 'auth/token': authToken, ...cliPropsToWrite } = cliProps;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* To not commit the auth token to the VCS, instead of adding it to the
|
|
114
|
+
* properties file (like the rest of props), it's added to the Sentry CLI
|
|
115
|
+
* config, which is added to the gitignore. This way makes the properties
|
|
116
|
+
* file safe to commit without exposing any auth tokens.
|
|
117
|
+
*/
|
|
118
|
+
if (authToken) {
|
|
119
|
+
try {
|
|
120
|
+
await fs.promises.appendFile(
|
|
121
|
+
SENTRYCLIRC_FILENAME,
|
|
122
|
+
this._sentryCli.dumpConfig({ auth: { token: authToken } }),
|
|
123
|
+
);
|
|
124
|
+
green(`✓ Successfully added the auth token to ${SENTRYCLIRC_FILENAME}`);
|
|
125
|
+
} catch {
|
|
126
|
+
red(
|
|
127
|
+
`⚠ Could not add the auth token to ${SENTRYCLIRC_FILENAME}, ` +
|
|
128
|
+
`please add it to identify your user account:\n${authToken}`,
|
|
129
|
+
);
|
|
130
|
+
nl();
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
red(
|
|
134
|
+
`⚠ Did not find an auth token, please add your token to ${SENTRYCLIRC_FILENAME}`,
|
|
135
|
+
);
|
|
136
|
+
l(
|
|
137
|
+
'To generate an auth token, visit https://sentry.io/settings/account/api/auth-tokens/',
|
|
138
|
+
);
|
|
139
|
+
l(
|
|
140
|
+
'To learn how to configure Sentry CLI, visit ' +
|
|
141
|
+
'https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#configure-sentry-cli',
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
await this._addToGitignore(
|
|
146
|
+
SENTRYCLIRC_FILENAME,
|
|
147
|
+
`⚠ Could not add ${SENTRYCLIRC_FILENAME} to ${GITIGNORE_FILENAME}, ` +
|
|
148
|
+
'please add it to not commit your auth key.',
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
await fs.promises.writeFile(
|
|
153
|
+
`./${PROPERTIES_FILENAME}`,
|
|
154
|
+
this._sentryCli.dumpProperties(cliPropsToWrite),
|
|
155
|
+
);
|
|
156
|
+
green(`✓ Successfully created sentry.properties`);
|
|
157
|
+
} catch {
|
|
158
|
+
red(`⚠ Could not add org and project data to ${PROPERTIES_FILENAME}`);
|
|
159
|
+
l(
|
|
160
|
+
'See docs for a manual setup: https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#configure-sentry-cli',
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
nl();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private async _addToGitignore(
|
|
167
|
+
filepath: string,
|
|
168
|
+
errorMsg: string,
|
|
169
|
+
): Promise<void> {
|
|
170
|
+
/**
|
|
171
|
+
* Don't check whether the given file is ignored because:
|
|
172
|
+
* 1. It's tricky to check it without git.
|
|
173
|
+
* 2. Git might not be installed or accessible.
|
|
174
|
+
* 3. It's convenient to use a module to interact with git, but it would
|
|
175
|
+
* increase the size x2 approximately. Docs say to run the Wizard without
|
|
176
|
+
* installing it, and duplicating the size would slow the set-up down.
|
|
177
|
+
* 4. The Wizard is meant to be run once.
|
|
178
|
+
* 5. A message is logged informing users it's been added to the gitignore.
|
|
179
|
+
* 6. It will be added to the gitignore as many times as it runs - not a big
|
|
180
|
+
* deal.
|
|
181
|
+
* 7. It's straightforward to remove it from the gitignore.
|
|
182
|
+
*/
|
|
183
|
+
try {
|
|
184
|
+
await fs.promises.appendFile(
|
|
185
|
+
GITIGNORE_FILENAME,
|
|
186
|
+
`\n# Sentry\n${filepath}\n`,
|
|
187
|
+
);
|
|
188
|
+
green(`✓ ${filepath} added to ${GITIGNORE_FILENAME}`);
|
|
189
|
+
} catch {
|
|
190
|
+
red(errorMsg);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
100
194
|
private _createNextConfig(configDirectory: string, dsn: any): void {
|
|
101
195
|
const templates = fs.readdirSync(configDirectory);
|
|
102
196
|
for (const template of templates) {
|
|
103
|
-
this._setTemplate(
|
|
197
|
+
this._setTemplate(
|
|
198
|
+
configDirectory,
|
|
199
|
+
template,
|
|
200
|
+
TEMPLATE_DESTINATIONS[template],
|
|
201
|
+
dsn,
|
|
202
|
+
);
|
|
104
203
|
}
|
|
105
204
|
red(
|
|
106
205
|
'⚠ Performance monitoring is enabled capturing 100% of transactions.\n' +
|
|
@@ -111,27 +210,54 @@ export class NextJs extends BaseIntegration {
|
|
|
111
210
|
|
|
112
211
|
private _setTemplate(
|
|
113
212
|
configDirectory: string,
|
|
114
|
-
|
|
213
|
+
templateFile: string,
|
|
214
|
+
destinationOptions: string[],
|
|
115
215
|
dsn: string,
|
|
116
216
|
): void {
|
|
117
|
-
const templatePath = path.join(configDirectory,
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
'
|
|
217
|
+
const templatePath = path.join(configDirectory, templateFile);
|
|
218
|
+
|
|
219
|
+
for (const destinationDir of destinationOptions) {
|
|
220
|
+
if (!fs.existsSync(destinationDir)) {
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const destinationPath = path.join(destinationDir, templateFile);
|
|
225
|
+
// in case the file in question already exists, we'll make a copy with
|
|
226
|
+
// `MERGEABLE_CONFIG_INFIX` inserted just before the extension, so as not
|
|
227
|
+
// to overwrite the existing file
|
|
228
|
+
const mergeableFilePath = path.join(
|
|
229
|
+
destinationDir,
|
|
230
|
+
this._spliceInPlace(
|
|
231
|
+
templateFile.split('.'),
|
|
232
|
+
-1,
|
|
233
|
+
0,
|
|
234
|
+
MERGEABLE_CONFIG_INFIX,
|
|
235
|
+
).join('.'),
|
|
132
236
|
);
|
|
133
|
-
|
|
237
|
+
|
|
238
|
+
if (!fs.existsSync(destinationPath)) {
|
|
239
|
+
this._fillAndCopyTemplate(templatePath, destinationPath, dsn);
|
|
240
|
+
} else if (!fs.existsSync(mergeableFilePath)) {
|
|
241
|
+
this._fillAndCopyTemplate(templatePath, mergeableFilePath, dsn);
|
|
242
|
+
red(
|
|
243
|
+
`File \`${templateFile}\` already exists, so created \`${mergeableFilePath}\`.\n` +
|
|
244
|
+
'Please merge those files.',
|
|
245
|
+
);
|
|
246
|
+
nl();
|
|
247
|
+
} else {
|
|
248
|
+
red(
|
|
249
|
+
`Both \`${templateFile}\` and \`${mergeableFilePath}\` already exist.\n` +
|
|
250
|
+
'Please merge those files.',
|
|
251
|
+
);
|
|
252
|
+
nl();
|
|
253
|
+
}
|
|
254
|
+
return;
|
|
134
255
|
}
|
|
256
|
+
|
|
257
|
+
red(
|
|
258
|
+
`Could not find appropriate destination for \`${templateFile}\`. Tried: ${destinationOptions}.`,
|
|
259
|
+
);
|
|
260
|
+
nl();
|
|
135
261
|
}
|
|
136
262
|
|
|
137
263
|
private _fillAndCopyTemplate(
|
|
@@ -144,63 +270,61 @@ export class NextJs extends BaseIntegration {
|
|
|
144
270
|
fs.writeFileSync(targetPath, filledTemplate);
|
|
145
271
|
}
|
|
146
272
|
|
|
147
|
-
private
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
['dependencies', packageName],
|
|
151
|
-
'0.0.0',
|
|
152
|
-
);
|
|
153
|
-
const devDepVersion = _.get(
|
|
154
|
-
appPackage,
|
|
155
|
-
['devDependencies', packageName],
|
|
156
|
-
'0.0.0',
|
|
157
|
-
);
|
|
273
|
+
private _checkUserNextVersion(packageName: string): boolean {
|
|
274
|
+
const depsVersion = _.get(appPackage, ['dependencies', packageName]);
|
|
275
|
+
const devDepsVersion = _.get(appPackage, ['devDependencies', packageName]);
|
|
158
276
|
|
|
159
|
-
if (
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
) {
|
|
163
|
-
red(`✗ ${packageName} isn't in your dependencies`);
|
|
164
|
-
red(` please install it with yarn/npm`);
|
|
277
|
+
if (!depsVersion && !devDepsVersion) {
|
|
278
|
+
red(`✗ ${packageName} isn't in your dependencies.`);
|
|
279
|
+
red(' Please install it with yarn/npm.');
|
|
165
280
|
return false;
|
|
166
281
|
} else if (
|
|
167
|
-
!this.
|
|
168
|
-
!this.
|
|
282
|
+
!this._fulfillsVersionRange(depsVersion) &&
|
|
283
|
+
!this._fulfillsVersionRange(devDepsVersion)
|
|
169
284
|
) {
|
|
170
285
|
red(
|
|
171
|
-
`✗ Your
|
|
286
|
+
`✗ Your \`package.json\` specifies a version of \`${packageName}\` outside of the compatible version range ${COMPATIBLE_NEXTJS_VERSIONS}.\n`,
|
|
172
287
|
);
|
|
173
288
|
return false;
|
|
174
289
|
} else {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
green(`✓ ${packageName} is installed`);
|
|
179
|
-
}
|
|
290
|
+
green(
|
|
291
|
+
`✓ A compatible version of \`${packageName}\` is specified in \`package.json\`.`,
|
|
292
|
+
);
|
|
180
293
|
return true;
|
|
181
294
|
}
|
|
182
295
|
}
|
|
183
296
|
|
|
184
|
-
private
|
|
185
|
-
// The latest version
|
|
186
|
-
// version, shouldn't be a blocker in the wizard.
|
|
297
|
+
private _fulfillsVersionRange(version: string): boolean {
|
|
298
|
+
// The latest version is currently 12.x, which is not yet supported.
|
|
187
299
|
if (version === 'latest') {
|
|
188
|
-
return
|
|
300
|
+
return false;
|
|
189
301
|
}
|
|
190
302
|
|
|
191
|
-
|
|
192
|
-
if (cleanedVersion) {
|
|
193
|
-
// gte(x, y) : true if x >= y
|
|
194
|
-
return gte(cleanedVersion, MIN_NEXTJS_VERSION);
|
|
195
|
-
}
|
|
303
|
+
let cleanedUserVersion, isRange;
|
|
196
304
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
if (
|
|
201
|
-
|
|
202
|
-
|
|
305
|
+
if (valid(version)) {
|
|
306
|
+
cleanedUserVersion = valid(version);
|
|
307
|
+
isRange = false;
|
|
308
|
+
} else if (validRange(version)) {
|
|
309
|
+
cleanedUserVersion = validRange(version);
|
|
310
|
+
isRange = true;
|
|
203
311
|
}
|
|
204
|
-
|
|
312
|
+
|
|
313
|
+
return (
|
|
314
|
+
!!cleanedUserVersion &&
|
|
315
|
+
(isRange
|
|
316
|
+
? subset(cleanedUserVersion, COMPATIBLE_NEXTJS_VERSIONS)
|
|
317
|
+
: satisfies(cleanedUserVersion, COMPATIBLE_NEXTJS_VERSIONS))
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
private _spliceInPlace(
|
|
322
|
+
arr: Array<any>,
|
|
323
|
+
start: number,
|
|
324
|
+
deleteCount: number,
|
|
325
|
+
...inserts: any[]
|
|
326
|
+
): Array<any> {
|
|
327
|
+
arr.splice(start, deleteCount, ...inserts);
|
|
328
|
+
return arr;
|
|
205
329
|
}
|
|
206
330
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentry/wizard",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.15",
|
|
4
4
|
"homepage": "https://github.com/getsentry/sentry-wizard",
|
|
5
5
|
"repository": "https://github.com/getsentry/sentry-wizard",
|
|
6
6
|
"description": "Sentry wizard helping you to configure your project",
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import NextErrorComponent from 'next/error';
|
|
2
|
+
|
|
3
|
+
import * as Sentry from '@sentry/nextjs';
|
|
4
|
+
|
|
5
|
+
const MyError = ({ statusCode, hasGetInitialPropsRun, err }) => {
|
|
6
|
+
if (!hasGetInitialPropsRun && err) {
|
|
7
|
+
// getInitialProps is not called in case of
|
|
8
|
+
// https://github.com/vercel/next.js/issues/8592. As a workaround, we pass
|
|
9
|
+
// err via _app.js so it can be captured
|
|
10
|
+
Sentry.captureException(err);
|
|
11
|
+
// Flushing is not required in this case as it only happens on the client
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return <NextErrorComponent statusCode={statusCode} />;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
MyError.getInitialProps = async ({ res, err, asPath }) => {
|
|
18
|
+
const errorInitialProps = await NextErrorComponent.getInitialProps({
|
|
19
|
+
res,
|
|
20
|
+
err,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Workaround for https://github.com/vercel/next.js/issues/8592, mark when
|
|
24
|
+
// getInitialProps has run
|
|
25
|
+
errorInitialProps.hasGetInitialPropsRun = true;
|
|
26
|
+
|
|
27
|
+
// Running on the server, the response object (`res`) is available.
|
|
28
|
+
//
|
|
29
|
+
// Next.js will pass an err on the server if a page's data fetching methods
|
|
30
|
+
// threw or returned a Promise that rejected
|
|
31
|
+
//
|
|
32
|
+
// Running on the client (browser), Next.js will provide an err if:
|
|
33
|
+
//
|
|
34
|
+
// - a page's `getInitialProps` threw or returned a Promise that rejected
|
|
35
|
+
// - an exception was thrown somewhere in the React lifecycle (render,
|
|
36
|
+
// componentDidMount, etc) that was caught by Next.js's React Error
|
|
37
|
+
// Boundary. Read more about what types of exceptions are caught by Error
|
|
38
|
+
// Boundaries: https://reactjs.org/docs/error-boundaries.html
|
|
39
|
+
|
|
40
|
+
if (err) {
|
|
41
|
+
Sentry.captureException(err);
|
|
42
|
+
|
|
43
|
+
// Flushing before returning is necessary if deploying to Vercel, see
|
|
44
|
+
// https://vercel.com/docs/platform/limits#streaming-responses
|
|
45
|
+
await Sentry.flush(2000);
|
|
46
|
+
|
|
47
|
+
return errorInitialProps;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// If this point is reached, getInitialProps was called without any
|
|
51
|
+
// information about what the error might be. This is unexpected and may
|
|
52
|
+
// indicate a bug introduced in Next.js, so record it in Sentry
|
|
53
|
+
Sentry.captureException(
|
|
54
|
+
new Error(`_error.js getInitialProps missing data at path: ${asPath}`),
|
|
55
|
+
);
|
|
56
|
+
await Sentry.flush(2000);
|
|
57
|
+
|
|
58
|
+
return errorInitialProps;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export default MyError;
|
|
@@ -9,7 +9,7 @@ const moduleExports = {
|
|
|
9
9
|
// Your existing module.exports
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
const
|
|
12
|
+
const sentryWebpackPluginOptions = {
|
|
13
13
|
// Additional config options for the Sentry Webpack plugin. Keep in mind that
|
|
14
14
|
// the following options are set automatically, and overriding them is not
|
|
15
15
|
// recommended:
|
|
@@ -23,4 +23,4 @@ const SentryWebpackPluginOptions = {
|
|
|
23
23
|
|
|
24
24
|
// Make sure adding Sentry options is the last code to run before exporting, to
|
|
25
25
|
// ensure that your source maps include changes from all other Webpack plugins
|
|
26
|
-
module.exports = withSentryConfig(moduleExports,
|
|
26
|
+
module.exports = withSentryConfig(moduleExports, sentryWebpackPluginOptions);
|