@sentry/wizard 3.12.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 (71) hide show
  1. package/CHANGELOG.md +13 -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 +6 -4
  6. package/dist/src/android/android-wizard.js.map +1 -1
  7. package/dist/src/nextjs/nextjs-wizard.js +5 -2
  8. package/dist/src/nextjs/nextjs-wizard.js.map +1 -1
  9. package/dist/src/nextjs/templates.d.ts +1 -1
  10. package/dist/src/nextjs/templates.js +2 -2
  11. package/dist/src/nextjs/templates.js.map +1 -1
  12. package/dist/src/remix/remix-wizard.js +8 -4
  13. package/dist/src/remix/remix-wizard.js.map +1 -1
  14. package/dist/src/remix/sdk-setup.d.ts +5 -1
  15. package/dist/src/remix/sdk-setup.js +3 -2
  16. package/dist/src/remix/sdk-setup.js.map +1 -1
  17. package/dist/src/sourcemaps/tools/sentry-cli.d.ts +9 -0
  18. package/dist/src/sourcemaps/tools/sentry-cli.js +26 -22
  19. package/dist/src/sourcemaps/tools/sentry-cli.js.map +1 -1
  20. package/dist/src/sourcemaps/tools/tsc.d.ts +6 -0
  21. package/dist/src/sourcemaps/tools/tsc.js +98 -17
  22. package/dist/src/sourcemaps/tools/tsc.js.map +1 -1
  23. package/dist/src/sourcemaps/tools/vite.js +3 -13
  24. package/dist/src/sourcemaps/tools/vite.js.map +1 -1
  25. package/dist/src/sourcemaps/tools/webpack.js +3 -13
  26. package/dist/src/sourcemaps/tools/webpack.js.map +1 -1
  27. package/dist/src/sveltekit/sdk-setup.js +122 -48
  28. package/dist/src/sveltekit/sdk-setup.js.map +1 -1
  29. package/dist/src/sveltekit/sveltekit-wizard.d.ts +1 -0
  30. package/dist/src/sveltekit/sveltekit-wizard.js +119 -44
  31. package/dist/src/sveltekit/sveltekit-wizard.js.map +1 -1
  32. package/dist/src/sveltekit/utils.d.ts +2 -0
  33. package/dist/src/sveltekit/utils.js +48 -0
  34. package/dist/src/sveltekit/utils.js.map +1 -0
  35. package/dist/src/utils/ast-utils.d.ts +70 -0
  36. package/dist/src/utils/ast-utils.js +152 -1
  37. package/dist/src/utils/ast-utils.js.map +1 -1
  38. package/dist/src/utils/clack-utils.d.ts +38 -6
  39. package/dist/src/utils/clack-utils.js +57 -51
  40. package/dist/src/utils/clack-utils.js.map +1 -1
  41. package/dist/src/utils/package-manager.d.ts +5 -0
  42. package/dist/src/utils/package-manager.js +11 -7
  43. package/dist/src/utils/package-manager.js.map +1 -1
  44. package/dist/test/sourcemaps/tools/sentry-cli.test.d.ts +1 -0
  45. package/dist/test/sourcemaps/tools/sentry-cli.test.js +112 -0
  46. package/dist/test/sourcemaps/tools/sentry-cli.test.js.map +1 -0
  47. package/dist/test/sourcemaps/tools/tsc.test.d.ts +1 -0
  48. package/dist/test/sourcemaps/tools/tsc.test.js +121 -0
  49. package/dist/test/sourcemaps/tools/tsc.test.js.map +1 -0
  50. package/dist/test/utils/ast-utils.test.js +157 -26
  51. package/dist/test/utils/ast-utils.test.js.map +1 -1
  52. package/lib/Steps/ChooseIntegration.ts +1 -0
  53. package/package.json +1 -1
  54. package/src/android/android-wizard.ts +8 -5
  55. package/src/nextjs/nextjs-wizard.ts +15 -3
  56. package/src/nextjs/templates.ts +3 -2
  57. package/src/remix/remix-wizard.ts +8 -11
  58. package/src/remix/sdk-setup.ts +8 -2
  59. package/src/sourcemaps/tools/sentry-cli.ts +16 -9
  60. package/src/sourcemaps/tools/tsc.ts +133 -28
  61. package/src/sourcemaps/tools/vite.ts +15 -39
  62. package/src/sourcemaps/tools/webpack.ts +16 -39
  63. package/src/sveltekit/sdk-setup.ts +109 -37
  64. package/src/sveltekit/sveltekit-wizard.ts +93 -25
  65. package/src/sveltekit/utils.ts +50 -0
  66. package/src/utils/ast-utils.ts +180 -0
  67. package/src/utils/clack-utils.ts +68 -49
  68. package/src/utils/package-manager.ts +12 -6
  69. package/test/sourcemaps/tools/sentry-cli.test.ts +51 -0
  70. package/test/sourcemaps/tools/tsc.test.ts +181 -0
  71. package/test/utils/ast-utils.test.ts +233 -32
@@ -53,10 +53,8 @@ async function runRemixWizardWithTelemetry(
53
53
  // We expect `@remix-run/dev` to be installed for every Remix project
54
54
  await ensurePackageIsInstalled(packageJson, '@remix-run/dev', 'Remix');
55
55
 
56
- const { selectedProject, authToken } = await getOrAskForProjectData(
57
- options,
58
- 'javascript-remix',
59
- );
56
+ const { selectedProject, authToken, sentryUrl } =
57
+ await getOrAskForProjectData(options, 'javascript-remix');
60
58
 
61
59
  await traceStep('Install Sentry SDK', () =>
62
60
  installPackage({
@@ -70,16 +68,15 @@ async function runRemixWizardWithTelemetry(
70
68
  const isTS = isUsingTypeScript();
71
69
  const isV2 = isRemixV2(remixConfig, packageJson);
72
70
 
73
- await addSentryCliConfig(
74
- authToken,
75
- sourceMapsCliSetupConfig,
76
- selectedProject.organization.slug,
77
- selectedProject.name,
78
- );
71
+ await addSentryCliConfig(authToken, sourceMapsCliSetupConfig);
79
72
 
80
73
  await traceStep('Update build script for sourcemap uploads', async () => {
81
74
  try {
82
- await updateBuildScript();
75
+ await updateBuildScript({
76
+ org: selectedProject.organization.slug,
77
+ project: selectedProject.name,
78
+ url: sentryUrl,
79
+ });
83
80
  } catch (e) {
84
81
  clack.log
85
82
  .warn(`Could not update build script to generate and upload sourcemaps.
@@ -151,7 +151,11 @@ export async function instrumentRootRoute(
151
151
  /* eslint-enable @typescript-eslint/no-unsafe-member-access */
152
152
  }
153
153
 
154
- export async function updateBuildScript(): Promise<void> {
154
+ export async function updateBuildScript(args: {
155
+ org: string;
156
+ project: string;
157
+ url?: string;
158
+ }): Promise<void> {
155
159
  /* eslint-disable @typescript-eslint/no-unsafe-member-access */
156
160
  // Add sourcemaps option to build script
157
161
  const packageJsonPath = path.join(process.cwd(), 'package.json');
@@ -166,7 +170,9 @@ export async function updateBuildScript(): Promise<void> {
166
170
 
167
171
  if (!packageJson.scripts.build) {
168
172
  packageJson.scripts.build =
169
- 'remix build --sourcemap && sentry-upload-sourcemaps';
173
+ `remix build --sourcemap && sentry-upload-sourcemaps --org ${args.org} --project ${args.project}` +
174
+ (args.url ? ` --url ${args.url}` : '');
175
+
170
176
  // eslint-disable-next-line @typescript-eslint/no-unsafe-call
171
177
  } else if (packageJson.scripts.build.includes('remix build')) {
172
178
  // eslint-disable-next-line @typescript-eslint/no-unsafe-call
@@ -1,5 +1,5 @@
1
1
  // @ts-ignore - clack is ESM and TS complains about that. It works though
2
- import clack from '@clack/prompts';
2
+ import * as clack from '@clack/prompts';
3
3
  import chalk from 'chalk';
4
4
  import * as Sentry from '@sentry/node';
5
5
  import * as path from 'path';
@@ -14,7 +14,7 @@ import {
14
14
  import { SourceMapUploadToolConfigurationOptions } from './types';
15
15
  import { hasPackageInstalled, PackageDotJson } from '../../utils/package-json';
16
16
  import { traceStep } from '../../telemetry';
17
- import { detectPackageManger } from '../../utils/package-manager';
17
+ import { detectPackageManger, NPM } from '../../utils/package-manager';
18
18
 
19
19
  const SENTRY_NPM_SCRIPT_NAME = 'sentry:sourcemaps';
20
20
 
@@ -194,7 +194,7 @@ async function askShouldAddToBuildCommand(): Promise<boolean> {
194
194
  *
195
195
  * @param packageDotJson The package.json which will be modified.
196
196
  */
197
- async function addSentryCommandToBuildCommand(
197
+ export async function addSentryCommandToBuildCommand(
198
198
  packageDotJson: PackageDotJson,
199
199
  ): Promise<void> {
200
200
  // This usually shouldn't happen because earlier we added the
@@ -205,8 +205,7 @@ async function addSentryCommandToBuildCommand(
205
205
  (s) => s !== SENTRY_NPM_SCRIPT_NAME,
206
206
  );
207
207
 
208
- const packageManager = detectPackageManger();
209
- const packageManagerName = packageManager?.name ?? 'npm';
208
+ const packageManager = detectPackageManger() ?? NPM;
210
209
 
211
210
  // Heuristic to pre-select the build command:
212
211
  // Often, 'build' is the prod build command, so we favour it.
@@ -221,7 +220,7 @@ async function addSentryCommandToBuildCommand(
221
220
  (await abortIfCancelled(
222
221
  clack.confirm({
223
222
  message: `Is ${chalk.cyan(
224
- `${packageManagerName} run ${buildCommand}`,
223
+ `${packageManager.runScriptCommand} ${buildCommand}`,
225
224
  )} your production build command?`,
226
225
  }),
227
226
  ));
@@ -229,7 +228,7 @@ async function addSentryCommandToBuildCommand(
229
228
  if (allNpmScripts.length && (!buildCommand || !isProdBuildCommand)) {
230
229
  buildCommand = await abortIfCancelled(
231
230
  clack.select({
232
- message: `Which ${packageManagerName} command in your ${chalk.cyan(
231
+ message: `Which ${packageManager.name} command in your ${chalk.cyan(
233
232
  'package.json',
234
233
  )} builds your application for production?`,
235
234
  options: allNpmScripts
@@ -252,10 +251,18 @@ Please add it manually to your prod build command.`,
252
251
  return;
253
252
  }
254
253
 
254
+ const oldCommand = packageDotJson.scripts[buildCommand];
255
+ if (!oldCommand) {
256
+ // very unlikely to happen but nevertheless
257
+ clack.log.warn(
258
+ `\`${buildCommand}\` doesn't seem to be part of your package.json scripts`,
259
+ );
260
+ return;
261
+ }
262
+
255
263
  packageDotJson.scripts[
256
264
  buildCommand
257
- // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
258
- ] = `${packageDotJson.scripts[buildCommand]} && ${packageManager} run ${SENTRY_NPM_SCRIPT_NAME}`;
265
+ ] = `${oldCommand} && ${packageManager.runScriptCommand} ${SENTRY_NPM_SCRIPT_NAME}`;
259
266
 
260
267
  await fs.promises.writeFile(
261
268
  path.join(process.cwd(), 'package.json'),
@@ -1,39 +1,144 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+
4
+ import * as recast from 'recast';
5
+
6
+ import * as Sentry from '@sentry/node';
7
+
1
8
  // @ts-ignore - clack is ESM and TS complains about that. It works though
2
- import clack, { select } from '@clack/prompts';
9
+ import * as clack from '@clack/prompts';
3
10
  import chalk from 'chalk';
4
- import { abortIfCancelled } from '../../utils/clack-utils';
5
-
6
- export async function configureTscSourcemapGenerationFlow(): Promise<void> {
7
- clack.log.step(
8
- `Add the following code to your ${chalk.bold(
9
- 'tsconfig.json',
10
- )} file: ${chalk.dim(
11
- '(This ensures that source maps are generated correctly)',
12
- )}`,
13
- );
14
11
 
15
- // Intentially logging directly to console here so that the code can be copied/pasted directly
16
- // eslint-disable-next-line no-console
17
- console.log(codeSnippet);
12
+ import {
13
+ askForToolConfigPath,
14
+ createNewConfigFile,
15
+ makeCodeSnippet,
16
+ showCopyPasteInstructions,
17
+ } from '../../utils/clack-utils';
18
+ import {
19
+ findFile,
20
+ getOrSetObjectProperty,
21
+ parseJsonC,
22
+ printJsonC,
23
+ setOrUpdateObjectProperty,
24
+ } from '../../utils/ast-utils';
25
+ import { debug } from '../../utils/debug';
18
26
 
19
- await abortIfCancelled(
20
- select({
21
- message: 'Did you update your config as shown in the snippet above?',
22
- options: [{ label: 'Yes, continue!', value: true }],
23
- initialValue: true,
24
- }),
25
- );
26
- }
27
+ const b = recast.types.builders;
27
28
 
28
- const codeSnippet = chalk.gray(`
29
- {
29
+ const getCodeSnippet = (colors: boolean) =>
30
+ makeCodeSnippet(colors, (unchanged, plus, _) =>
31
+ unchanged(
32
+ `{
30
33
  "compilerOptions": {
31
- ${chalk.greenBright('"sourceMap": true,')}
32
- ${chalk.greenBright('"inlineSources": true,')}
34
+ ${plus('"sourceMap": true,')}
35
+ ${plus('"inlineSources": true,')}
33
36
 
34
37
  // Set \`sourceRoot\` to "/" to strip the build path prefix from
35
38
  // generated source code references. This will improve issue grouping in Sentry.
36
- ${chalk.greenBright('"sourceRoot": "/"')}
39
+ ${plus('"sourceRoot": "/"')}
40
+ }
41
+ }`,
42
+ ),
43
+ );
44
+
45
+ export async function configureTscSourcemapGenerationFlow(): Promise<void> {
46
+ const tsConfigPath =
47
+ findFile(path.join(process.cwd(), 'tsconfig'), ['.json']) ??
48
+ (await askForToolConfigPath('TypeScript', 'tsconfig.json'));
49
+
50
+ let successfullyAdded = false;
51
+ if (tsConfigPath) {
52
+ successfullyAdded = await enableSourcemaps(tsConfigPath);
53
+ } else {
54
+ successfullyAdded = await createNewConfigFile(
55
+ path.join(process.cwd(), 'tsconfig.json'),
56
+ getCodeSnippet(false),
57
+ );
58
+ Sentry.setTag('created-new-config', successfullyAdded ? 'success' : 'fail');
59
+ }
60
+
61
+ if (successfullyAdded) {
62
+ Sentry.setTag('ast-mod', 'success');
63
+ clack.log.info(
64
+ `We recommend checking the ${
65
+ tsConfigPath ? 'modified' : 'added'
66
+ } file after the wizard finished to ensure it works with your build setup.`,
67
+ );
68
+ } else {
69
+ Sentry.setTag('ast-mod', 'fail');
70
+ await showCopyPasteInstructions(
71
+ 'tsconfig.json',
72
+ getCodeSnippet(true),
73
+ 'This ensures that source maps are generated correctly',
74
+ );
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Modifies tsconfig.json (@param tsConfigPath) to enable source maps generation.
80
+ *
81
+ * Exported only for testing
82
+ */
83
+ export async function enableSourcemaps(tsConfigPath: string): Promise<boolean> {
84
+ try {
85
+ const tsConfig = await fs.promises.readFile(tsConfigPath, 'utf-8');
86
+
87
+ const { ast, jsonObject } = parseJsonC(tsConfig.toString());
88
+
89
+ if (!jsonObject || !ast) {
90
+ // this will only happen if the input file isn't valid JSON-C
91
+ Sentry.setTag('ast-mod-fail-reason', 'original-file-invalid');
92
+ return false;
93
+ }
94
+
95
+ const compilerOptionsProp = getOrSetObjectProperty(
96
+ jsonObject,
97
+ 'compilerOptions',
98
+ b.objectExpression([]),
99
+ );
100
+
101
+ const compilerOptionsObj = compilerOptionsProp.value;
102
+
103
+ if (!compilerOptionsObj || compilerOptionsObj.type !== 'ObjectExpression') {
104
+ // a valid compilerOptions prop should always be an object expression
105
+ Sentry.setTag('ast-mod-fail-reason', 'original-file-invalid');
106
+ return false;
107
+ }
108
+
109
+ setOrUpdateObjectProperty(
110
+ compilerOptionsObj,
111
+ 'sourceMap',
112
+ b.booleanLiteral(true),
113
+ );
114
+
115
+ setOrUpdateObjectProperty(
116
+ compilerOptionsObj,
117
+ 'inlineSources',
118
+ b.booleanLiteral(true),
119
+ );
120
+
121
+ setOrUpdateObjectProperty(
122
+ compilerOptionsObj,
123
+ 'sourceRoot',
124
+ b.stringLiteral('/'),
125
+ 'Set `sourceRoot` to "/" to strip the build path prefix\nfrom generated source code references.\nThis improves issue grouping in Sentry.',
126
+ );
127
+
128
+ const code = printJsonC(ast);
129
+
130
+ await fs.promises.writeFile(tsConfigPath, code);
131
+
132
+ clack.log.success(
133
+ `Enabled source maps generation in ${chalk.cyan(
134
+ path.basename(tsConfigPath || 'tsconfig.json'),
135
+ )}.`,
136
+ );
137
+
138
+ return true;
139
+ } catch (e) {
140
+ debug(e);
141
+ Sentry.setTag('ast-mod-fail-reason', 'insertion-fail');
142
+ return false;
37
143
  }
38
144
  }
39
- `);
@@ -19,6 +19,7 @@ import {
19
19
  createNewConfigFile,
20
20
  getPackageDotJson,
21
21
  installPackage,
22
+ makeCodeSnippet,
22
23
  showCopyPasteInstructions,
23
24
  } from '../../utils/clack-utils';
24
25
  import { hasPackageInstalled } from '../../utils/package-json';
@@ -36,52 +37,27 @@ import { debug } from '../../utils/debug';
36
37
  const getViteConfigSnippet = (
37
38
  options: SourceMapUploadToolConfigurationOptions,
38
39
  colors: boolean,
39
- ) => {
40
- const rawImportStmt =
41
- 'import { sentryVitePlugin } from "@sentry/vite-plugin";';
42
- const rawGenerateSourceMapsOption =
43
- 'sourcemap: true, // Source map generation must be turned on';
44
- const rawSentryVitePluginFunction = `sentryVitePlugin({
45
- authToken: process.env.SENTRY_AUTH_TOKEN,
46
- org: "${options.orgSlug}",
47
- project: "${options.projectSlug}",${
48
- options.selfHosted ? `\n url: "${options.url}",` : ''
49
- }
50
- }),`;
51
-
52
- const importStmt = colors ? chalk.greenBright(rawImportStmt) : rawImportStmt;
53
- const generateSourceMapsOption = colors
54
- ? chalk.greenBright(rawGenerateSourceMapsOption)
55
- : rawGenerateSourceMapsOption;
56
- const sentryVitePluginFunction = colors
57
- ? chalk.greenBright(rawSentryVitePluginFunction)
58
- : rawSentryVitePluginFunction;
59
-
60
- const code = getViteConfigContent(
61
- importStmt,
62
- generateSourceMapsOption,
63
- sentryVitePluginFunction,
64
- );
65
- return colors ? chalk.gray(code) : code;
66
- };
67
-
68
- const getViteConfigContent = (
69
- importStmt: string,
70
- generateSourceMapsOption: string,
71
- sentryVitePluginFunction: string,
72
- ) => `import { defineConfig } from "vite";
73
- ${importStmt}
40
+ ) =>
41
+ makeCodeSnippet(colors, (unchanged, plus, _) =>
42
+ unchanged(`import { defineConfig } from "vite";
43
+ ${plus('import { sentryVitePlugin } from "@sentry/vite-plugin";')}
74
44
 
75
45
  export default defineConfig({
76
46
  build: {
77
- ${generateSourceMapsOption}
47
+ ${plus('sourcemap: true, // Source map generation must be turned on')}
78
48
  },
79
49
  plugins: [
80
50
  // Put the Sentry vite plugin after all other plugins
81
- ${sentryVitePluginFunction}
51
+ ${plus(`sentryVitePlugin({
52
+ authToken: process.env.SENTRY_AUTH_TOKEN,
53
+ org: "${options.orgSlug}",
54
+ project: "${options.projectSlug}",${
55
+ options.selfHosted ? `\n url: "${options.url}",` : ''
56
+ }
57
+ }),`)}
82
58
  ],
83
- });
84
- `;
59
+ });`),
60
+ );
85
61
 
86
62
  export const configureVitePlugin: SourceMapUploadToolConfigurationFunction =
87
63
  async (options) => {
@@ -18,6 +18,7 @@ import {
18
18
  createNewConfigFile,
19
19
  getPackageDotJson,
20
20
  installPackage,
21
+ makeCodeSnippet,
21
22
  showCopyPasteInstructions,
22
23
  } from '../../utils/clack-utils';
23
24
  import { hasPackageInstalled } from '../../utils/package-json';
@@ -33,51 +34,27 @@ import { debug } from '../../utils/debug';
33
34
  const getCodeSnippet = (
34
35
  options: SourceMapUploadToolConfigurationOptions,
35
36
  colors: boolean,
36
- ) => {
37
- const rawImportStmt =
38
- 'const { sentryWebpackPlugin } = require("@sentry/webpack-plugin");';
39
- const rawGenerateSourceMapsOption =
40
- 'devtool: "source-map", // Source map generation must be turned on';
41
- const rawSentryWebpackPluginFunction = `sentryWebpackPlugin({
42
- authToken: process.env.SENTRY_AUTH_TOKEN,
43
- org: "${options.orgSlug}",
44
- project: "${options.projectSlug}",${
45
- options.selfHosted ? `\n url: "${options.url}",` : ''
46
- }
47
- })`;
48
-
49
- const importStmt = colors ? chalk.greenBright(rawImportStmt) : rawImportStmt;
50
- const generateSourceMapsOption = colors
51
- ? chalk.greenBright(rawGenerateSourceMapsOption)
52
- : rawGenerateSourceMapsOption;
53
- const sentryWebpackPluginFunction = colors
54
- ? chalk.greenBright(rawSentryWebpackPluginFunction)
55
- : rawSentryWebpackPluginFunction;
56
-
57
- const code = getWebpackConfigContent(
58
- importStmt,
59
- generateSourceMapsOption,
60
- sentryWebpackPluginFunction,
61
- );
62
-
63
- return colors ? chalk.gray(code) : code;
64
- };
65
-
66
- const getWebpackConfigContent = (
67
- importStmt: string,
68
- generateSourceMapsOption: string,
69
- sentryWebpackPluginFunction: string,
70
- ) => `${importStmt}
37
+ ) =>
38
+ makeCodeSnippet(colors, (unchanged, plus) =>
39
+ unchanged(`${plus(
40
+ 'const { sentryWebpackPlugin } = require("@sentry/webpack-plugin");',
41
+ )}
71
42
 
72
43
  module.exports = {
73
44
  // ... other options
74
- ${generateSourceMapsOption},
45
+ ${plus('devtool: "source-map", // Source map generation must be turned on')}
75
46
  plugins: [
76
47
  // Put the Sentry Webpack plugin after all other plugins
77
- ${sentryWebpackPluginFunction},
48
+ ${plus(`sentryWebpackPlugin({
49
+ authToken: process.env.SENTRY_AUTH_TOKEN,
50
+ org: "${options.orgSlug}",
51
+ project: "${options.projectSlug}",${
52
+ options.selfHosted ? `\n url: "${options.url}",` : ''
53
+ }
54
+ }),`)}
78
55
  ],
79
- }
80
- `;
56
+ }`),
57
+ );
81
58
 
82
59
  export const configureWebPackPlugin: SourceMapUploadToolConfigurationFunction =
83
60
  async (options) => {
@@ -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
@@ -20,6 +22,7 @@ import { findFile, hasSentryContent } from '../utils/ast-utils';
20
22
  import * as recast from 'recast';
21
23
  import x = recast.types;
22
24
  import t = x.namedTypes;
25
+ import { traceStep } from '../telemetry';
23
26
 
24
27
  const SVELTE_CONFIG_FILE = 'svelte.config.js';
25
28
 
@@ -59,19 +62,25 @@ export async function createOrMergeSvelteKitFiles(
59
62
 
60
63
  const { dsn } = projectInfo;
61
64
 
65
+ Sentry.setTag(
66
+ 'client-hooks-file-strategy',
67
+ originalClientHooksFile ? 'merge' : 'create',
68
+ );
62
69
  if (!originalClientHooksFile) {
63
70
  clack.log.info('No client hooks file found, creating a new one.');
64
71
  await createNewHooksFile(`${clientHooksPath}.${fileEnding}`, 'client', dsn);
72
+ } else {
73
+ await mergeHooksFile(originalClientHooksFile, 'client', dsn);
65
74
  }
75
+
76
+ Sentry.setTag(
77
+ 'server-hooks-file-strategy',
78
+ originalServerHooksFile ? 'merge' : 'create',
79
+ );
66
80
  if (!originalServerHooksFile) {
67
81
  clack.log.info('No server hooks file found, creating a new one.');
68
82
  await createNewHooksFile(`${serverHooksPath}.${fileEnding}`, 'server', dsn);
69
- }
70
-
71
- if (originalClientHooksFile) {
72
- await mergeHooksFile(originalClientHooksFile, 'client', dsn);
73
- }
74
- if (originalServerHooksFile) {
83
+ } else {
75
84
  await mergeHooksFile(originalServerHooksFile, 'server', dsn);
76
85
  }
77
86
 
@@ -124,6 +133,7 @@ async function createNewHooksFile(
124
133
  await fs.promises.writeFile(hooksFileDest, filledTemplate);
125
134
 
126
135
  clack.log.success(`Created ${hooksFileDest}`);
136
+ Sentry.setTag(`created-${hooktype}-hooks`, 'success');
127
137
  }
128
138
 
129
139
  /**
@@ -143,6 +153,9 @@ async function mergeHooksFile(
143
153
  dsn: string,
144
154
  ): Promise<void> {
145
155
  const originalHooksMod = await loadFile(hooksFile);
156
+
157
+ const file: 'server-hooks' | 'client-hooks' = `${hookType}-hooks`;
158
+
146
159
  if (hasSentryContent(originalHooksMod.$ast as t.Program)) {
147
160
  // We don't want to mess with files that already have Sentry content.
148
161
  // Let's just bail out at this point.
@@ -152,32 +165,59 @@ async function mergeHooksFile(
152
165
  )} already contains Sentry code.
153
166
  Skipping adding Sentry functionality to.`,
154
167
  );
168
+ Sentry.setTag(`modified-${file}`, 'fail');
169
+ Sentry.setTag(`${file}-fail-reason`, 'has-sentry-content');
155
170
  return;
156
171
  }
157
172
 
158
- originalHooksMod.imports.$add({
159
- from: '@sentry/sveltekit',
160
- imported: '*',
161
- local: 'Sentry',
162
- });
173
+ await modifyAndRecordFail(
174
+ () =>
175
+ originalHooksMod.imports.$add({
176
+ from: '@sentry/sveltekit',
177
+ imported: '*',
178
+ local: 'Sentry',
179
+ }),
180
+ 'import-injection',
181
+ file,
182
+ );
163
183
 
164
- if (hookType === 'client') {
165
- insertClientInitCall(dsn, originalHooksMod);
166
- } else {
167
- insertServerInitCall(dsn, originalHooksMod);
168
- }
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
+ );
169
195
 
170
- wrapHandleError(originalHooksMod);
196
+ await modifyAndRecordFail(
197
+ () => wrapHandleError(originalHooksMod),
198
+ 'wrap-handle-error',
199
+ file,
200
+ );
171
201
 
172
202
  if (hookType === 'server') {
173
- wrapHandle(originalHooksMod);
203
+ await modifyAndRecordFail(
204
+ () => wrapHandle(originalHooksMod),
205
+ 'wrap-handle',
206
+ 'server-hooks',
207
+ );
174
208
  }
175
209
 
176
- const modifiedCode = originalHooksMod.generate().code;
177
-
178
- 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
+ );
179
218
 
180
219
  clack.log.success(`Added Sentry code to ${hooksFile}`);
220
+ Sentry.setTag(`modified-${hookType}-hooks`, 'success');
181
221
  }
182
222
 
183
223
  function insertClientInitCall(
@@ -410,32 +450,45 @@ async function modifyViteConfig(
410
450
  )} already contains Sentry code.
411
451
  Skipping adding Sentry functionality to.`,
412
452
  );
453
+ Sentry.setTag(`modified-vite-cfg`, 'fail');
454
+ Sentry.setTag(`vite-cfg-fail-reason`, 'has-sentry-content');
413
455
  return;
414
456
  }
415
457
 
416
- addVitePlugin(viteModule, {
417
- imported: 'sentrySvelteKit',
418
- from: '@sentry/sveltekit',
419
- constructor: 'sentrySvelteKit',
420
- options: {
421
- sourceMapsUploadOptions: {
422
- org,
423
- project,
424
- ...(selfHosted && { url }),
425
- },
426
- },
427
- index: 0,
428
- });
429
-
430
- 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
+ );
431
476
 
432
- 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
+ );
433
485
  } catch (e) {
434
486
  debug(e);
435
487
  await showFallbackViteCopyPasteSnippet(
436
488
  viteConfigPath,
437
489
  getViteConfigCodeSnippet(org, project, selfHosted, url),
438
490
  );
491
+ Sentry.captureException('Sveltekit Vite Config Modification Fail');
439
492
  }
440
493
  }
441
494
 
@@ -512,3 +565,22 @@ function getInitCallInsertionIndex(originalHooksModAST: Program): number {
512
565
  : 0;
513
566
  return initCallInsertionIndex;
514
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
+ }