@sentry/wizard 3.11.0 → 3.13.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.
Files changed (91) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/lib/Steps/ChooseIntegration.js +1 -0
  3. package/dist/lib/Steps/ChooseIntegration.js.map +1 -1
  4. package/dist/package.json +1 -1
  5. package/dist/src/android/android-wizard.js +14 -4
  6. package/dist/src/android/android-wizard.js.map +1 -1
  7. package/dist/src/android/code-tools.d.ts +8 -0
  8. package/dist/src/android/code-tools.js +20 -8
  9. package/dist/src/android/code-tools.js.map +1 -1
  10. package/dist/src/android/gradle.js +6 -1
  11. package/dist/src/android/gradle.js.map +1 -1
  12. package/dist/src/nextjs/nextjs-wizard.js +5 -2
  13. package/dist/src/nextjs/nextjs-wizard.js.map +1 -1
  14. package/dist/src/nextjs/templates.d.ts +1 -1
  15. package/dist/src/nextjs/templates.js +2 -2
  16. package/dist/src/nextjs/templates.js.map +1 -1
  17. package/dist/src/remix/remix-wizard.js +8 -4
  18. package/dist/src/remix/remix-wizard.js.map +1 -1
  19. package/dist/src/remix/sdk-setup.d.ts +5 -1
  20. package/dist/src/remix/sdk-setup.js +3 -2
  21. package/dist/src/remix/sdk-setup.js.map +1 -1
  22. package/dist/src/sourcemaps/tools/sentry-cli.d.ts +9 -0
  23. package/dist/src/sourcemaps/tools/sentry-cli.js +26 -22
  24. package/dist/src/sourcemaps/tools/sentry-cli.js.map +1 -1
  25. package/dist/src/sourcemaps/tools/tsc.d.ts +6 -0
  26. package/dist/src/sourcemaps/tools/tsc.js +98 -17
  27. package/dist/src/sourcemaps/tools/tsc.js.map +1 -1
  28. package/dist/src/sourcemaps/tools/vite.js +39 -124
  29. package/dist/src/sourcemaps/tools/vite.js.map +1 -1
  30. package/dist/src/sourcemaps/tools/webpack.d.ts +6 -1
  31. package/dist/src/sourcemaps/tools/webpack.js +280 -25
  32. package/dist/src/sourcemaps/tools/webpack.js.map +1 -1
  33. package/dist/src/sveltekit/sdk-setup.js +123 -49
  34. package/dist/src/sveltekit/sdk-setup.js.map +1 -1
  35. package/dist/src/sveltekit/sveltekit-wizard.d.ts +1 -0
  36. package/dist/src/sveltekit/sveltekit-wizard.js +119 -44
  37. package/dist/src/sveltekit/sveltekit-wizard.js.map +1 -1
  38. package/dist/src/sveltekit/utils.d.ts +2 -0
  39. package/dist/src/sveltekit/utils.js +48 -0
  40. package/dist/src/sveltekit/utils.js.map +1 -0
  41. package/dist/src/utils/ast-utils.d.ts +77 -3
  42. package/dist/src/utils/ast-utils.js +172 -6
  43. package/dist/src/utils/ast-utils.js.map +1 -1
  44. package/dist/src/utils/clack-utils.d.ts +85 -1
  45. package/dist/src/utils/clack-utils.js +214 -51
  46. package/dist/src/utils/clack-utils.js.map +1 -1
  47. package/dist/src/utils/package-manager.d.ts +5 -0
  48. package/dist/src/utils/package-manager.js +11 -7
  49. package/dist/src/utils/package-manager.js.map +1 -1
  50. package/dist/test/android/code-tools.test.d.ts +1 -0
  51. package/dist/test/android/code-tools.test.js +34 -0
  52. package/dist/test/android/code-tools.test.js.map +1 -0
  53. package/dist/test/sourcemaps/tools/sentry-cli.test.d.ts +1 -0
  54. package/dist/test/sourcemaps/tools/sentry-cli.test.js +112 -0
  55. package/dist/test/sourcemaps/tools/sentry-cli.test.js.map +1 -0
  56. package/dist/test/sourcemaps/tools/tsc.test.d.ts +1 -0
  57. package/dist/test/sourcemaps/tools/tsc.test.js +121 -0
  58. package/dist/test/sourcemaps/tools/tsc.test.js.map +1 -0
  59. package/dist/test/sourcemaps/tools/webpack.test.d.ts +1 -0
  60. package/dist/test/sourcemaps/tools/webpack.test.js +179 -0
  61. package/dist/test/sourcemaps/tools/webpack.test.js.map +1 -0
  62. package/dist/test/utils/ast-utils.test.js +181 -15
  63. package/dist/test/utils/ast-utils.test.js.map +1 -1
  64. package/dist/test/utils/clack-utils.test.d.ts +1 -0
  65. package/dist/test/utils/clack-utils.test.js +200 -0
  66. package/dist/test/utils/clack-utils.test.js.map +1 -0
  67. package/lib/Steps/ChooseIntegration.ts +1 -0
  68. package/package.json +1 -1
  69. package/src/android/android-wizard.ts +16 -5
  70. package/src/android/code-tools.ts +21 -7
  71. package/src/android/gradle.ts +6 -1
  72. package/src/nextjs/nextjs-wizard.ts +15 -3
  73. package/src/nextjs/templates.ts +3 -2
  74. package/src/remix/remix-wizard.ts +8 -11
  75. package/src/remix/sdk-setup.ts +8 -2
  76. package/src/sourcemaps/tools/sentry-cli.ts +16 -9
  77. package/src/sourcemaps/tools/tsc.ts +133 -28
  78. package/src/sourcemaps/tools/vite.ts +37 -127
  79. package/src/sourcemaps/tools/webpack.ts +343 -27
  80. package/src/sveltekit/sdk-setup.ts +115 -39
  81. package/src/sveltekit/sveltekit-wizard.ts +93 -25
  82. package/src/sveltekit/utils.ts +50 -0
  83. package/src/utils/ast-utils.ts +203 -7
  84. package/src/utils/clack-utils.ts +211 -44
  85. package/src/utils/package-manager.ts +12 -6
  86. package/test/android/code-tools.test.ts +49 -0
  87. package/test/sourcemaps/tools/sentry-cli.test.ts +51 -0
  88. package/test/sourcemaps/tools/tsc.test.ts +181 -0
  89. package/test/sourcemaps/tools/webpack.test.ts +303 -0
  90. package/test/utils/ast-utils.test.ts +240 -20
  91. package/test/utils/clack-utils.test.ts +142 -0
@@ -1,11 +1,25 @@
1
+ import * as path from 'path';
2
+ import * as fs from 'fs';
3
+
1
4
  // @ts-ignore - clack is ESM and TS complains about that. It works though
2
- import clack, { select } from '@clack/prompts';
5
+ import * as clack from '@clack/prompts';
3
6
  import chalk from 'chalk';
7
+
8
+ import * as recast from 'recast';
9
+ import x = recast.types;
10
+ import t = x.namedTypes;
11
+
12
+ import * as Sentry from '@sentry/node';
13
+
4
14
  import {
5
15
  abortIfCancelled,
6
16
  addDotEnvSentryBuildPluginFile,
17
+ askForToolConfigPath,
18
+ createNewConfigFile,
7
19
  getPackageDotJson,
8
20
  installPackage,
21
+ makeCodeSnippet,
22
+ showCopyPasteInstructions,
9
23
  } from '../../utils/clack-utils';
10
24
  import { hasPackageInstalled } from '../../utils/package-json';
11
25
 
@@ -14,28 +28,33 @@ import {
14
28
  SourceMapUploadToolConfigurationOptions,
15
29
  } from './types';
16
30
 
17
- const getCodeSnippet = (options: SourceMapUploadToolConfigurationOptions) =>
18
- chalk.gray(`
19
- ${chalk.greenBright(
20
- 'const { sentryWebpackPlugin } = require("@sentry/webpack-plugin");',
21
- )}
31
+ import { findFile, hasSentryContent } from '../../utils/ast-utils';
32
+ import { debug } from '../../utils/debug';
33
+
34
+ const getCodeSnippet = (
35
+ options: SourceMapUploadToolConfigurationOptions,
36
+ colors: boolean,
37
+ ) =>
38
+ makeCodeSnippet(colors, (unchanged, plus) =>
39
+ unchanged(`${plus(
40
+ 'const { sentryWebpackPlugin } = require("@sentry/webpack-plugin");',
41
+ )}
22
42
 
23
43
  module.exports = {
24
- // ... other config options
25
- ${chalk.greenBright(
26
- 'devtool: "source-map", // Source map generation must be turned on',
27
- )}
44
+ // ... other options
45
+ ${plus('devtool: "source-map", // Source map generation must be turned on')}
28
46
  plugins: [
29
- ${chalk.greenBright(`sentryWebpackPlugin({
47
+ // Put the Sentry Webpack plugin after all other plugins
48
+ ${plus(`sentryWebpackPlugin({
30
49
  authToken: process.env.SENTRY_AUTH_TOKEN,
31
50
  org: "${options.orgSlug}",
32
51
  project: "${options.projectSlug}",${
33
52
  options.selfHosted ? `\n url: "${options.url}",` : ''
34
- }
35
- })`)},
53
+ }
54
+ }),`)}
36
55
  ],
37
- };
38
- `);
56
+ }`),
57
+ );
39
58
 
40
59
  export const configureWebPackPlugin: SourceMapUploadToolConfigurationFunction =
41
60
  async (options) => {
@@ -47,21 +66,318 @@ export const configureWebPackPlugin: SourceMapUploadToolConfigurationFunction =
47
66
  ),
48
67
  });
49
68
 
50
- clack.log.step(
51
- `Add the following code to your ${chalk.bold('webpack.config.js')} file:`,
52
- );
69
+ const webpackConfigPath =
70
+ findFile(path.resolve(process.cwd(), 'webpack.config')) ??
71
+ (await askForToolConfigPath('Webpack', 'webpack.config.js'));
72
+
73
+ let successfullyAdded = false;
74
+ if (webpackConfigPath) {
75
+ successfullyAdded = await modifyWebpackConfig(webpackConfigPath, options);
76
+ } else {
77
+ successfullyAdded = await createNewConfigFile(
78
+ path.join(process.cwd(), 'webpack.config.js'),
79
+ getCodeSnippet(options, false),
80
+ 'More information about Webpack configs: https://vitejs.dev/config/',
81
+ );
82
+ Sentry.setTag(
83
+ 'created-new-config',
84
+ successfullyAdded ? 'success' : 'fail',
85
+ );
86
+ }
53
87
 
54
- // Intentially logging directly to console here so that the code can be copied/pasted directly
55
- // eslint-disable-next-line no-console
56
- console.log(getCodeSnippet(options));
88
+ if (successfullyAdded) {
89
+ clack.log.info(
90
+ `We recommend checking the ${
91
+ webpackConfigPath ? 'modified' : 'added'
92
+ } file after the wizard finished to ensure it works with your build setup.`,
93
+ );
94
+
95
+ Sentry.setTag('ast-mod', 'success');
96
+ } else {
97
+ Sentry.setTag('ast-mod', 'fail');
98
+ await showCopyPasteInstructions(
99
+ path.basename(webpackConfigPath || 'webpack.config.js'),
100
+ getCodeSnippet(options, true),
101
+ );
102
+ }
103
+
104
+ await addDotEnvSentryBuildPluginFile(options.authToken);
105
+ };
106
+
107
+ /**
108
+ * Modifies a webpack config file to enable source map generation and add the Sentry webpack plugin
109
+ * exported only for testing
110
+ */
111
+ export async function modifyWebpackConfig(
112
+ webpackConfigPath: string,
113
+ options: SourceMapUploadToolConfigurationOptions,
114
+ ): Promise<boolean> {
115
+ try {
116
+ const webpackConfig = await fs.promises.readFile(webpackConfigPath, {
117
+ encoding: 'utf-8',
118
+ });
57
119
 
58
- await abortIfCancelled(
59
- select({
60
- message: 'Did you copy the snippet above?',
61
- options: [{ label: 'Yes, continue!', value: true }],
120
+ const prettyConfigFilename = chalk.cyan(path.basename(webpackConfigPath));
121
+
122
+ // no idea why recast returns any here, this is dumb :/
123
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
124
+ const program = recast.parse(webpackConfig.toString()).program as t.Program;
125
+
126
+ if (!(await shouldModifyWebpackConfig(program, prettyConfigFilename))) {
127
+ // Sentry tag is set in shouldModifyWebpackConfig
128
+ return false;
129
+ }
130
+
131
+ const exportStmt = getCjsModuleExports(program);
132
+ if (!exportStmt) {
133
+ // We only care about CJS at the moment since it's probably the most widely used format for webpack configs.
134
+ debug(`Could not find module.exports = {...} in ${webpackConfigPath}.`);
135
+ Sentry.setTag('ast-mod-fail-reason', 'config-object-not-found');
136
+ return false;
137
+ }
138
+
139
+ const configObject = getWebpackConfigObject(exportStmt, program);
140
+
141
+ if (!configObject) {
142
+ debug(`Couldn't find config object in ${webpackConfigPath}`);
143
+ Sentry.setTag('ast-mod-fail-reason', 'config-object-not-found');
144
+ return false;
145
+ }
146
+
147
+ const enabledSourcemaps = enableSourcemapsGeneration(configObject);
148
+
149
+ if (enabledSourcemaps) {
150
+ clack.log.success(
151
+ `Enabled source map generation in ${prettyConfigFilename}.`,
152
+ );
153
+ } else {
154
+ clack.log.warn(
155
+ `Couldn't enable source maps generation in ${prettyConfigFilename} Please follow the instructions below.`,
156
+ );
157
+ Sentry.setTag('ast-mod-fail-reason', 'insertion-fail');
158
+ return false;
159
+ }
160
+
161
+ const addedPlugin = addSentryWebpackPlugin(program, configObject, options);
162
+ if (addedPlugin) {
163
+ clack.log.success(
164
+ `Added Sentry webpack plugin to ${prettyConfigFilename}.`,
165
+ );
166
+ } else {
167
+ clack.log.warn(
168
+ `Couldn't add Sentry webpack plugin to ${prettyConfigFilename}. Please follow the instructions below.`,
169
+ );
170
+ Sentry.setTag('ast-mod-fail-reason', 'insertion-fail');
171
+ return false;
172
+ }
173
+
174
+ const code = recast.print(program).code;
175
+ await fs.promises.writeFile(webpackConfigPath, code);
176
+
177
+ return true;
178
+ } catch (e) {
179
+ Sentry.setTag('ast-mod-fail-reason', 'insertion-fail');
180
+ debug(e);
181
+ return false;
182
+ }
183
+ }
184
+
185
+ async function shouldModifyWebpackConfig(
186
+ program: t.Program,
187
+ prettyConfigFilename: string,
188
+ ) {
189
+ if (hasSentryContent(program)) {
190
+ const shouldContinue = await abortIfCancelled(
191
+ clack.select({
192
+ message: `Seems like ${prettyConfigFilename} already contains Sentry-related code. Should the wizard modify it anyway?`,
193
+ options: [
194
+ {
195
+ label: 'Yes, add the Sentry Webpack plugin',
196
+ value: true,
197
+ },
198
+ {
199
+ label: 'No, show me instructions to manually add the plugin',
200
+ value: false,
201
+ },
202
+ ],
62
203
  initialValue: true,
63
204
  }),
64
205
  );
65
206
 
66
- await addDotEnvSentryBuildPluginFile(options.authToken);
67
- };
207
+ if (!shouldContinue) {
208
+ Sentry.setTag('ast-mod-fail-reason', 'has-sentry-content');
209
+ return false;
210
+ }
211
+ }
212
+
213
+ return true;
214
+ }
215
+
216
+ function addSentryWebpackPlugin(
217
+ program: t.Program,
218
+ configObj: t.ObjectExpression,
219
+ options: SourceMapUploadToolConfigurationOptions,
220
+ ) {
221
+ const b = addSentryWebpackPluginImport(program);
222
+
223
+ const sentryPluginCall = b.callExpression(
224
+ b.identifier('sentryWebpackPlugin'),
225
+ [
226
+ b.objectExpression([
227
+ b.objectProperty(
228
+ b.identifier('authToken'),
229
+ b.identifier('process.env.SENTRY_AUTH_TOKEN'),
230
+ ),
231
+ b.objectProperty(b.identifier('org'), b.stringLiteral(options.orgSlug)),
232
+ b.objectProperty(
233
+ b.identifier('project'),
234
+ b.stringLiteral(options.projectSlug),
235
+ ),
236
+ ...(options.selfHosted
237
+ ? [
238
+ b.objectProperty(
239
+ b.identifier('url'),
240
+ b.stringLiteral(options.url),
241
+ ),
242
+ ]
243
+ : []),
244
+ ]),
245
+ ],
246
+ );
247
+
248
+ const pluginsProp = configObj.properties.find(
249
+ (p): p is t.Property =>
250
+ p.type === 'Property' &&
251
+ p.key.type === 'Identifier' &&
252
+ p.key.name === 'plugins',
253
+ );
254
+
255
+ if (pluginsProp) {
256
+ if (pluginsProp.value.type === 'ArrayExpression') {
257
+ pluginsProp.value.elements.push(sentryPluginCall);
258
+ } else {
259
+ pluginsProp.value = b.arrayExpression([sentryPluginCall]);
260
+ }
261
+ return true;
262
+ }
263
+
264
+ configObj.properties.push(
265
+ b.objectProperty(
266
+ b.identifier('plugins'),
267
+ b.arrayExpression([sentryPluginCall]),
268
+ ),
269
+ );
270
+
271
+ return true;
272
+ }
273
+
274
+ function addSentryWebpackPluginImport(program: t.Program) {
275
+ const b = recast.types.builders;
276
+
277
+ const sentryPluginRequireStmt = b.variableDeclaration('const', [
278
+ b.variableDeclarator(
279
+ b.objectPattern([
280
+ b.objectProperty.from({
281
+ key: b.identifier('sentryWebpackPlugin'),
282
+ value: b.identifier('sentryWebpackPlugin'),
283
+ shorthand: true,
284
+ }),
285
+ ]),
286
+ b.callExpression(b.identifier('require'), [
287
+ b.stringLiteral('@sentry/webpack-plugin'),
288
+ ]),
289
+ ),
290
+ ]);
291
+
292
+ program.body.unshift(sentryPluginRequireStmt);
293
+ return b;
294
+ }
295
+
296
+ function enableSourcemapsGeneration(configObj: t.ObjectExpression): boolean {
297
+ const b = recast.types.builders;
298
+
299
+ const devtoolProp = configObj.properties.find(
300
+ (p): p is t.Property =>
301
+ p.type === 'Property' &&
302
+ p.key.type === 'Identifier' &&
303
+ p.key.name === 'devtool',
304
+ );
305
+
306
+ if (devtoolProp) {
307
+ // devtool can have quite a lot of source maps values.
308
+ // see: https://webpack.js.org/configuration/devtool/#devtool
309
+ // For Sentry to work best, we should set it to "source-map" or "hidden-source-map"
310
+ // Heuristic:
311
+ // - all values that contain "hidden" will be set to "hidden-source-map"
312
+ // - all other values will be set to "source-map"
313
+ if (
314
+ (devtoolProp.value.type === 'Literal' ||
315
+ devtoolProp.value.type === 'StringLiteral') &&
316
+ devtoolProp.value.value?.toString().startsWith('hidden-')
317
+ ) {
318
+ devtoolProp.value = b.stringLiteral('hidden-source-map');
319
+ } else {
320
+ devtoolProp.value = b.stringLiteral('source-map');
321
+ }
322
+ return true;
323
+ }
324
+
325
+ configObj.properties.push(
326
+ b.objectProperty(b.identifier('devtool'), b.stringLiteral('source-map')),
327
+ );
328
+
329
+ return true;
330
+ }
331
+
332
+ function getWebpackConfigObject(
333
+ moduleExports: t.AssignmentExpression,
334
+ program: t.Program,
335
+ ): t.ObjectExpression | undefined {
336
+ const rhs = moduleExports.right;
337
+ if (rhs.type === 'ObjectExpression') {
338
+ return rhs;
339
+ }
340
+ if (rhs.type === 'Identifier') {
341
+ const configId = rhs.name;
342
+
343
+ const configDeclaration = program.body.find(
344
+ (s): s is t.VariableDeclaration =>
345
+ s.type === 'VariableDeclaration' &&
346
+ !!s.declarations.find(
347
+ (d) =>
348
+ d.type === 'VariableDeclarator' &&
349
+ d.id.type === 'Identifier' &&
350
+ d.id.name === configId,
351
+ ),
352
+ );
353
+
354
+ const declarator = configDeclaration?.declarations.find(
355
+ (d): d is t.VariableDeclarator =>
356
+ d.type === 'VariableDeclarator' &&
357
+ d.id.type === 'Identifier' &&
358
+ d.id.name === configId,
359
+ );
360
+
361
+ return declarator?.init?.type === 'ObjectExpression'
362
+ ? declarator.init
363
+ : undefined;
364
+ }
365
+
366
+ return undefined;
367
+ }
368
+
369
+ function getCjsModuleExports(
370
+ program: t.Program,
371
+ ): t.AssignmentExpression | undefined {
372
+ const moduleExports = program.body.find(
373
+ (s): s is t.ExpressionStatement =>
374
+ s.type === 'ExpressionStatement' &&
375
+ s.expression.type === 'AssignmentExpression' &&
376
+ s.expression.left.type === 'MemberExpression' &&
377
+ s.expression.left.object.type === 'Identifier' &&
378
+ s.expression.left.object.name === 'module' &&
379
+ s.expression.left.property.type === 'Identifier' &&
380
+ s.expression.left.property.name === 'exports',
381
+ );
382
+ return moduleExports?.expression as t.AssignmentExpression;
383
+ }
@@ -4,6 +4,8 @@ import * as path from 'path';
4
4
  import * as url from 'url';
5
5
  import chalk from 'chalk';
6
6
 
7
+ import * as Sentry from '@sentry/node';
8
+
7
9
  // @ts-ignore - clack is ESM and TS complains about that. It works though
8
10
  import clack from '@clack/prompts';
9
11
  // @ts-ignore - magicast is ESM and TS complains about that. It works though
@@ -17,6 +19,11 @@ import { abortIfCancelled, isUsingTypeScript } from '../utils/clack-utils';
17
19
  import { debug } from '../utils/debug';
18
20
  import { findFile, hasSentryContent } from '../utils/ast-utils';
19
21
 
22
+ import * as recast from 'recast';
23
+ import x = recast.types;
24
+ import t = x.namedTypes;
25
+ import { traceStep } from '../telemetry';
26
+
20
27
  const SVELTE_CONFIG_FILE = 'svelte.config.js';
21
28
 
22
29
  export type PartialSvelteConfig = {
@@ -55,19 +62,25 @@ export async function createOrMergeSvelteKitFiles(
55
62
 
56
63
  const { dsn } = projectInfo;
57
64
 
65
+ Sentry.setTag(
66
+ 'client-hooks-file-strategy',
67
+ originalClientHooksFile ? 'merge' : 'create',
68
+ );
58
69
  if (!originalClientHooksFile) {
59
70
  clack.log.info('No client hooks file found, creating a new one.');
60
71
  await createNewHooksFile(`${clientHooksPath}.${fileEnding}`, 'client', dsn);
72
+ } else {
73
+ await mergeHooksFile(originalClientHooksFile, 'client', dsn);
61
74
  }
75
+
76
+ Sentry.setTag(
77
+ 'server-hooks-file-strategy',
78
+ originalServerHooksFile ? 'merge' : 'create',
79
+ );
62
80
  if (!originalServerHooksFile) {
63
81
  clack.log.info('No server hooks file found, creating a new one.');
64
82
  await createNewHooksFile(`${serverHooksPath}.${fileEnding}`, 'server', dsn);
65
- }
66
-
67
- if (originalClientHooksFile) {
68
- await mergeHooksFile(originalClientHooksFile, 'client', dsn);
69
- }
70
- if (originalServerHooksFile) {
83
+ } else {
71
84
  await mergeHooksFile(originalServerHooksFile, 'server', dsn);
72
85
  }
73
86
 
@@ -120,6 +133,7 @@ async function createNewHooksFile(
120
133
  await fs.promises.writeFile(hooksFileDest, filledTemplate);
121
134
 
122
135
  clack.log.success(`Created ${hooksFileDest}`);
136
+ Sentry.setTag(`created-${hooktype}-hooks`, 'success');
123
137
  }
124
138
 
125
139
  /**
@@ -139,7 +153,10 @@ async function mergeHooksFile(
139
153
  dsn: string,
140
154
  ): Promise<void> {
141
155
  const originalHooksMod = await loadFile(hooksFile);
142
- if (hasSentryContent(originalHooksMod)) {
156
+
157
+ const file: 'server-hooks' | 'client-hooks' = `${hookType}-hooks`;
158
+
159
+ if (hasSentryContent(originalHooksMod.$ast as t.Program)) {
143
160
  // We don't want to mess with files that already have Sentry content.
144
161
  // Let's just bail out at this point.
145
162
  clack.log.warn(
@@ -148,32 +165,59 @@ async function mergeHooksFile(
148
165
  )} already contains Sentry code.
149
166
  Skipping adding Sentry functionality to.`,
150
167
  );
168
+ Sentry.setTag(`modified-${file}`, 'fail');
169
+ Sentry.setTag(`${file}-fail-reason`, 'has-sentry-content');
151
170
  return;
152
171
  }
153
172
 
154
- originalHooksMod.imports.$add({
155
- from: '@sentry/sveltekit',
156
- imported: '*',
157
- local: 'Sentry',
158
- });
173
+ await modifyAndRecordFail(
174
+ () =>
175
+ originalHooksMod.imports.$add({
176
+ from: '@sentry/sveltekit',
177
+ imported: '*',
178
+ local: 'Sentry',
179
+ }),
180
+ 'import-injection',
181
+ file,
182
+ );
159
183
 
160
- if (hookType === 'client') {
161
- insertClientInitCall(dsn, originalHooksMod);
162
- } else {
163
- insertServerInitCall(dsn, originalHooksMod);
164
- }
184
+ await modifyAndRecordFail(
185
+ () => {
186
+ if (hookType === 'client') {
187
+ insertClientInitCall(dsn, originalHooksMod);
188
+ } else {
189
+ insertServerInitCall(dsn, originalHooksMod);
190
+ }
191
+ },
192
+ 'init-call-injection',
193
+ file,
194
+ );
165
195
 
166
- wrapHandleError(originalHooksMod);
196
+ await modifyAndRecordFail(
197
+ () => wrapHandleError(originalHooksMod),
198
+ 'wrap-handle-error',
199
+ file,
200
+ );
167
201
 
168
202
  if (hookType === 'server') {
169
- wrapHandle(originalHooksMod);
203
+ await modifyAndRecordFail(
204
+ () => wrapHandle(originalHooksMod),
205
+ 'wrap-handle',
206
+ 'server-hooks',
207
+ );
170
208
  }
171
209
 
172
- const modifiedCode = originalHooksMod.generate().code;
173
-
174
- await fs.promises.writeFile(hooksFile, modifiedCode);
210
+ await modifyAndRecordFail(
211
+ async () => {
212
+ const modifiedCode = originalHooksMod.generate().code;
213
+ await fs.promises.writeFile(hooksFile, modifiedCode);
214
+ },
215
+ 'write-file',
216
+ file,
217
+ );
175
218
 
176
219
  clack.log.success(`Added Sentry code to ${hooksFile}`);
220
+ Sentry.setTag(`modified-${hookType}-hooks`, 'success');
177
221
  }
178
222
 
179
223
  function insertClientInitCall(
@@ -399,39 +443,52 @@ async function modifyViteConfig(
399
443
  try {
400
444
  const viteModule = parseModule(viteConfigContent);
401
445
 
402
- if (hasSentryContent(viteModule)) {
446
+ if (hasSentryContent(viteModule.$ast as t.Program)) {
403
447
  clack.log.warn(
404
448
  `File ${chalk.cyan(
405
449
  path.basename(viteConfigPath),
406
450
  )} already contains Sentry code.
407
451
  Skipping adding Sentry functionality to.`,
408
452
  );
453
+ Sentry.setTag(`modified-vite-cfg`, 'fail');
454
+ Sentry.setTag(`vite-cfg-fail-reason`, 'has-sentry-content');
409
455
  return;
410
456
  }
411
457
 
412
- addVitePlugin(viteModule, {
413
- imported: 'sentrySvelteKit',
414
- from: '@sentry/sveltekit',
415
- constructor: 'sentrySvelteKit',
416
- options: {
417
- sourceMapsUploadOptions: {
418
- org,
419
- project,
420
- ...(selfHosted && { url }),
421
- },
422
- },
423
- index: 0,
424
- });
425
-
426
- const code = generateCode(viteModule.$ast).code;
458
+ await modifyAndRecordFail(
459
+ () =>
460
+ addVitePlugin(viteModule, {
461
+ imported: 'sentrySvelteKit',
462
+ from: '@sentry/sveltekit',
463
+ constructor: 'sentrySvelteKit',
464
+ options: {
465
+ sourceMapsUploadOptions: {
466
+ org,
467
+ project,
468
+ ...(selfHosted && { url }),
469
+ },
470
+ },
471
+ index: 0,
472
+ }),
473
+ 'add-vite-plugin',
474
+ 'vite-cfg',
475
+ );
427
476
 
428
- await fs.promises.writeFile(viteConfigPath, code);
477
+ await modifyAndRecordFail(
478
+ async () => {
479
+ const code = generateCode(viteModule.$ast).code;
480
+ await fs.promises.writeFile(viteConfigPath, code);
481
+ },
482
+ 'write-file',
483
+ 'vite-cfg',
484
+ );
429
485
  } catch (e) {
430
486
  debug(e);
431
487
  await showFallbackViteCopyPasteSnippet(
432
488
  viteConfigPath,
433
489
  getViteConfigCodeSnippet(org, project, selfHosted, url),
434
490
  );
491
+ Sentry.captureException('Sveltekit Vite Config Modification Fail');
435
492
  }
436
493
  }
437
494
 
@@ -508,3 +565,22 @@ function getInitCallInsertionIndex(originalHooksModAST: Program): number {
508
565
  : 0;
509
566
  return initCallInsertionIndex;
510
567
  }
568
+
569
+ /**
570
+ * Applies the @param modifyCallback and records Sentry tags if the call failed.
571
+ * In case of a failure, a tag is set with @param reason as a fail reason
572
+ * and the error is rethrown.
573
+ */
574
+ async function modifyAndRecordFail<T>(
575
+ modifyCallback: () => T | Promise<T>,
576
+ reason: string,
577
+ fileType: 'server-hooks' | 'client-hooks' | 'vite-cfg',
578
+ ): Promise<void> {
579
+ try {
580
+ await traceStep(`${fileType}-${reason}`, modifyCallback);
581
+ } catch (e) {
582
+ Sentry.setTag(`modified-${fileType}`, 'fail');
583
+ Sentry.setTag(`${fileType}-mod-fail-reason`, reason);
584
+ throw e;
585
+ }
586
+ }