@sentry/wizard 3.11.0 → 3.12.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 (46) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/package.json +1 -1
  3. package/dist/src/android/android-wizard.js +8 -0
  4. package/dist/src/android/android-wizard.js.map +1 -1
  5. package/dist/src/android/code-tools.d.ts +8 -0
  6. package/dist/src/android/code-tools.js +20 -8
  7. package/dist/src/android/code-tools.js.map +1 -1
  8. package/dist/src/android/gradle.js +6 -1
  9. package/dist/src/android/gradle.js.map +1 -1
  10. package/dist/src/sourcemaps/tools/vite.js +36 -111
  11. package/dist/src/sourcemaps/tools/vite.js.map +1 -1
  12. package/dist/src/sourcemaps/tools/webpack.d.ts +6 -1
  13. package/dist/src/sourcemaps/tools/webpack.js +290 -25
  14. package/dist/src/sourcemaps/tools/webpack.js.map +1 -1
  15. package/dist/src/sveltekit/sdk-setup.js +2 -2
  16. package/dist/src/sveltekit/sdk-setup.js.map +1 -1
  17. package/dist/src/utils/ast-utils.d.ts +7 -3
  18. package/dist/src/utils/ast-utils.js +20 -5
  19. package/dist/src/utils/ast-utils.js.map +1 -1
  20. package/dist/src/utils/clack-utils.d.ts +52 -0
  21. package/dist/src/utils/clack-utils.js +169 -12
  22. package/dist/src/utils/clack-utils.js.map +1 -1
  23. package/dist/test/android/code-tools.test.d.ts +1 -0
  24. package/dist/test/android/code-tools.test.js +34 -0
  25. package/dist/test/android/code-tools.test.js.map +1 -0
  26. package/dist/test/sourcemaps/tools/webpack.test.d.ts +1 -0
  27. package/dist/test/sourcemaps/tools/webpack.test.js +179 -0
  28. package/dist/test/sourcemaps/tools/webpack.test.js.map +1 -0
  29. package/dist/test/utils/ast-utils.test.js +42 -7
  30. package/dist/test/utils/ast-utils.test.js.map +1 -1
  31. package/dist/test/utils/clack-utils.test.d.ts +1 -0
  32. package/dist/test/utils/clack-utils.test.js +200 -0
  33. package/dist/test/utils/clack-utils.test.js.map +1 -0
  34. package/package.json +1 -1
  35. package/src/android/android-wizard.ts +8 -0
  36. package/src/android/code-tools.ts +21 -7
  37. package/src/android/gradle.ts +6 -1
  38. package/src/sourcemaps/tools/vite.ts +22 -88
  39. package/src/sourcemaps/tools/webpack.ts +369 -30
  40. package/src/sveltekit/sdk-setup.ts +6 -2
  41. package/src/utils/ast-utils.ts +23 -7
  42. package/src/utils/clack-utils.ts +150 -2
  43. package/test/android/code-tools.test.ts +49 -0
  44. package/test/sourcemaps/tools/webpack.test.ts +303 -0
  45. package/test/utils/ast-utils.test.ts +28 -9
  46. package/test/utils/clack-utils.test.ts +142 -0
@@ -15,8 +15,11 @@ import chalk from 'chalk';
15
15
  import {
16
16
  abortIfCancelled,
17
17
  addDotEnvSentryBuildPluginFile,
18
+ askForToolConfigPath,
19
+ createNewConfigFile,
18
20
  getPackageDotJson,
19
21
  installPackage,
22
+ showCopyPasteInstructions,
20
23
  } from '../../utils/clack-utils';
21
24
  import { hasPackageInstalled } from '../../utils/package-json';
22
25
 
@@ -91,58 +94,43 @@ export const configureVitePlugin: SourceMapUploadToolConfigurationFunction =
91
94
  });
92
95
 
93
96
  const viteConfigPath =
94
- findFile(path.resolve(process.cwd(), 'vite.config')) ||
95
- (await askForViteConfigPath());
97
+ findFile(path.resolve(process.cwd(), 'vite.config')) ??
98
+ (await askForToolConfigPath('Vite', 'vite.config.js'));
96
99
 
97
100
  let successfullyAdded = false;
98
101
  if (viteConfigPath) {
99
102
  successfullyAdded = await addVitePluginToConfig(viteConfigPath, options);
100
103
  } else {
101
- successfullyAdded = await createNewViteConfig(options);
104
+ successfullyAdded = await createNewConfigFile(
105
+ path.join(process.cwd(), 'vite.config.js'),
106
+ getViteConfigSnippet(options, false),
107
+ 'More information about vite configs: https://vitejs.dev/config/',
108
+ );
109
+ Sentry.setTag(
110
+ 'created-new-config',
111
+ successfullyAdded ? 'success' : 'fail',
112
+ );
102
113
  }
103
114
 
104
115
  if (successfullyAdded) {
116
+ clack.log.info(
117
+ `We recommend checking the ${
118
+ viteConfigPath ? 'modified' : 'added'
119
+ } file after the wizard finished to ensure it works with your build setup.`,
120
+ );
121
+
105
122
  Sentry.setTag('ast-mod', 'success');
106
123
  } else {
107
124
  Sentry.setTag('ast-mod', 'fail');
108
125
  await showCopyPasteInstructions(
109
126
  path.basename(viteConfigPath || 'vite.config.js'),
110
- options,
127
+ getViteConfigSnippet(options, true),
111
128
  );
112
129
  }
113
130
 
114
131
  await addDotEnvSentryBuildPluginFile(options.authToken);
115
132
  };
116
133
 
117
- async function createNewViteConfig(
118
- options: SourceMapUploadToolConfigurationOptions,
119
- ): Promise<boolean> {
120
- try {
121
- await fs.promises.writeFile(
122
- 'vite.config.js',
123
- getViteConfigSnippet(options, false),
124
- );
125
- Sentry.setTag('created-new-config', 'success');
126
- return true;
127
- } catch (e) {
128
- debug(e);
129
- Sentry.setTag('created-new-config', 'fail');
130
- clack.log.warn(
131
- `Could not create a new ${chalk.cyan(
132
- 'vite.config.js',
133
- )} file. Please create one manually and follow the instructions below.`,
134
- );
135
-
136
- clack.log.info(
137
- chalk.gray(
138
- 'More information about vite configs: https://vitejs.dev/config/',
139
- ),
140
- );
141
-
142
- return false;
143
- }
144
- }
145
-
146
134
  export async function addVitePluginToConfig(
147
135
  viteConfigPath: string,
148
136
  options: SourceMapUploadToolConfigurationOptions,
@@ -156,7 +144,7 @@ export async function addVitePluginToConfig(
156
144
 
157
145
  const mod = parseModule(viteConfigContent);
158
146
 
159
- if (hasSentryContent(mod)) {
147
+ if (hasSentryContent(mod.$ast as t.Program)) {
160
148
  const shouldContinue = await abortIfCancelled(
161
149
  clack.select({
162
150
  message: `${prettyViteConfigFilename} already contains Sentry-related code. Should the wizard modify it anyway?`,
@@ -215,60 +203,6 @@ export async function addVitePluginToConfig(
215
203
  }
216
204
  }
217
205
 
218
- async function showCopyPasteInstructions(
219
- viteConfigFilename: string,
220
- options: SourceMapUploadToolConfigurationOptions,
221
- ) {
222
- clack.log.step(
223
- `Add the following code to your ${chalk.cyan(viteConfigFilename)} file:`,
224
- );
225
-
226
- // Intentionally logging directly to console here so that the code can be copied/pasted directly
227
- // eslint-disable-next-line no-console
228
- console.log(`\n${getViteConfigSnippet(options, true)}`);
229
-
230
- await abortIfCancelled(
231
- clack.select({
232
- message: 'Did you copy the snippet above?',
233
- options: [{ label: 'Yes, continue!', value: true }],
234
- initialValue: true,
235
- }),
236
- );
237
- }
238
-
239
- async function askForViteConfigPath(): Promise<string | undefined> {
240
- const hasViteConfig = await abortIfCancelled(
241
- clack.confirm({
242
- message: `Do you have a vite config file (e.g. ${chalk.cyan(
243
- 'vite.config.js',
244
- )}?`,
245
- initialValue: true,
246
- }),
247
- );
248
-
249
- if (!hasViteConfig) {
250
- return undefined;
251
- }
252
-
253
- return await abortIfCancelled(
254
- clack.text({
255
- message: 'Please enter the path to your vite config file:',
256
- placeholder: `.${path.sep}vite.config.js`,
257
- validate: (value) => {
258
- if (!value) {
259
- return 'Please enter a path.';
260
- }
261
-
262
- try {
263
- fs.accessSync(value);
264
- } catch {
265
- return 'Could not access the file at this path.';
266
- }
267
- },
268
- }),
269
- );
270
- }
271
-
272
206
  function enableSourcemapGeneration(program: t.Program): boolean {
273
207
  const configObj = getViteConfigObject(program);
274
208
 
@@ -1,11 +1,24 @@
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
+ showCopyPasteInstructions,
9
22
  } from '../../utils/clack-utils';
10
23
  import { hasPackageInstalled } from '../../utils/package-json';
11
24
 
@@ -14,28 +27,57 @@ import {
14
27
  SourceMapUploadToolConfigurationOptions,
15
28
  } from './types';
16
29
 
17
- const getCodeSnippet = (options: SourceMapUploadToolConfigurationOptions) =>
18
- chalk.gray(`
19
- ${chalk.greenBright(
20
- 'const { sentryWebpackPlugin } = require("@sentry/webpack-plugin");',
21
- )}
30
+ import { findFile, hasSentryContent } from '../../utils/ast-utils';
31
+ import { debug } from '../../utils/debug';
22
32
 
23
- module.exports = {
24
- // ... other config options
25
- ${chalk.greenBright(
26
- 'devtool: "source-map", // Source map generation must be turned on',
27
- )}
28
- plugins: [
29
- ${chalk.greenBright(`sentryWebpackPlugin({
33
+ const getCodeSnippet = (
34
+ options: SourceMapUploadToolConfigurationOptions,
35
+ 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({
30
42
  authToken: process.env.SENTRY_AUTH_TOKEN,
31
43
  org: "${options.orgSlug}",
32
44
  project: "${options.projectSlug}",${
33
- options.selfHosted ? `\n url: "${options.url}",` : ''
34
- }
35
- })`)},
36
- ],
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;
37
64
  };
38
- `);
65
+
66
+ const getWebpackConfigContent = (
67
+ importStmt: string,
68
+ generateSourceMapsOption: string,
69
+ sentryWebpackPluginFunction: string,
70
+ ) => `${importStmt}
71
+
72
+ module.exports = {
73
+ // ... other options
74
+ ${generateSourceMapsOption},
75
+ plugins: [
76
+ // Put the Sentry Webpack plugin after all other plugins
77
+ ${sentryWebpackPluginFunction},
78
+ ],
79
+ }
80
+ `;
39
81
 
40
82
  export const configureWebPackPlugin: SourceMapUploadToolConfigurationFunction =
41
83
  async (options) => {
@@ -47,21 +89,318 @@ export const configureWebPackPlugin: SourceMapUploadToolConfigurationFunction =
47
89
  ),
48
90
  });
49
91
 
50
- clack.log.step(
51
- `Add the following code to your ${chalk.bold('webpack.config.js')} file:`,
52
- );
92
+ const webpackConfigPath =
93
+ findFile(path.resolve(process.cwd(), 'webpack.config')) ??
94
+ (await askForToolConfigPath('Webpack', 'webpack.config.js'));
95
+
96
+ let successfullyAdded = false;
97
+ if (webpackConfigPath) {
98
+ successfullyAdded = await modifyWebpackConfig(webpackConfigPath, options);
99
+ } else {
100
+ successfullyAdded = await createNewConfigFile(
101
+ path.join(process.cwd(), 'webpack.config.js'),
102
+ getCodeSnippet(options, false),
103
+ 'More information about Webpack configs: https://vitejs.dev/config/',
104
+ );
105
+ Sentry.setTag(
106
+ 'created-new-config',
107
+ successfullyAdded ? 'success' : 'fail',
108
+ );
109
+ }
110
+
111
+ if (successfullyAdded) {
112
+ clack.log.info(
113
+ `We recommend checking the ${
114
+ webpackConfigPath ? 'modified' : 'added'
115
+ } file after the wizard finished to ensure it works with your build setup.`,
116
+ );
117
+
118
+ Sentry.setTag('ast-mod', 'success');
119
+ } else {
120
+ Sentry.setTag('ast-mod', 'fail');
121
+ await showCopyPasteInstructions(
122
+ path.basename(webpackConfigPath || 'webpack.config.js'),
123
+ getCodeSnippet(options, true),
124
+ );
125
+ }
126
+
127
+ await addDotEnvSentryBuildPluginFile(options.authToken);
128
+ };
129
+
130
+ /**
131
+ * Modifies a webpack config file to enable source map generation and add the Sentry webpack plugin
132
+ * exported only for testing
133
+ */
134
+ export async function modifyWebpackConfig(
135
+ webpackConfigPath: string,
136
+ options: SourceMapUploadToolConfigurationOptions,
137
+ ): Promise<boolean> {
138
+ try {
139
+ const webpackConfig = await fs.promises.readFile(webpackConfigPath, {
140
+ encoding: 'utf-8',
141
+ });
142
+
143
+ const prettyConfigFilename = chalk.cyan(path.basename(webpackConfigPath));
144
+
145
+ // no idea why recast returns any here, this is dumb :/
146
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
147
+ const program = recast.parse(webpackConfig.toString()).program as t.Program;
148
+
149
+ if (!(await shouldModifyWebpackConfig(program, prettyConfigFilename))) {
150
+ // Sentry tag is set in shouldModifyWebpackConfig
151
+ return false;
152
+ }
153
+
154
+ const exportStmt = getCjsModuleExports(program);
155
+ if (!exportStmt) {
156
+ // We only care about CJS at the moment since it's probably the most widely used format for webpack configs.
157
+ debug(`Could not find module.exports = {...} in ${webpackConfigPath}.`);
158
+ Sentry.setTag('ast-mod-fail-reason', 'config-object-not-found');
159
+ return false;
160
+ }
161
+
162
+ const configObject = getWebpackConfigObject(exportStmt, program);
163
+
164
+ if (!configObject) {
165
+ debug(`Couldn't find config object in ${webpackConfigPath}`);
166
+ Sentry.setTag('ast-mod-fail-reason', 'config-object-not-found');
167
+ return false;
168
+ }
53
169
 
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));
170
+ const enabledSourcemaps = enableSourcemapsGeneration(configObject);
57
171
 
58
- await abortIfCancelled(
59
- select({
60
- message: 'Did you copy the snippet above?',
61
- options: [{ label: 'Yes, continue!', value: true }],
172
+ if (enabledSourcemaps) {
173
+ clack.log.success(
174
+ `Enabled source map generation in ${prettyConfigFilename}.`,
175
+ );
176
+ } else {
177
+ clack.log.warn(
178
+ `Couldn't enable source maps generation in ${prettyConfigFilename} Please follow the instructions below.`,
179
+ );
180
+ Sentry.setTag('ast-mod-fail-reason', 'insertion-fail');
181
+ return false;
182
+ }
183
+
184
+ const addedPlugin = addSentryWebpackPlugin(program, configObject, options);
185
+ if (addedPlugin) {
186
+ clack.log.success(
187
+ `Added Sentry webpack plugin to ${prettyConfigFilename}.`,
188
+ );
189
+ } else {
190
+ clack.log.warn(
191
+ `Couldn't add Sentry webpack plugin to ${prettyConfigFilename}. Please follow the instructions below.`,
192
+ );
193
+ Sentry.setTag('ast-mod-fail-reason', 'insertion-fail');
194
+ return false;
195
+ }
196
+
197
+ const code = recast.print(program).code;
198
+ await fs.promises.writeFile(webpackConfigPath, code);
199
+
200
+ return true;
201
+ } catch (e) {
202
+ Sentry.setTag('ast-mod-fail-reason', 'insertion-fail');
203
+ debug(e);
204
+ return false;
205
+ }
206
+ }
207
+
208
+ async function shouldModifyWebpackConfig(
209
+ program: t.Program,
210
+ prettyConfigFilename: string,
211
+ ) {
212
+ if (hasSentryContent(program)) {
213
+ const shouldContinue = await abortIfCancelled(
214
+ clack.select({
215
+ message: `Seems like ${prettyConfigFilename} already contains Sentry-related code. Should the wizard modify it anyway?`,
216
+ options: [
217
+ {
218
+ label: 'Yes, add the Sentry Webpack plugin',
219
+ value: true,
220
+ },
221
+ {
222
+ label: 'No, show me instructions to manually add the plugin',
223
+ value: false,
224
+ },
225
+ ],
62
226
  initialValue: true,
63
227
  }),
64
228
  );
65
229
 
66
- await addDotEnvSentryBuildPluginFile(options.authToken);
67
- };
230
+ if (!shouldContinue) {
231
+ Sentry.setTag('ast-mod-fail-reason', 'has-sentry-content');
232
+ return false;
233
+ }
234
+ }
235
+
236
+ return true;
237
+ }
238
+
239
+ function addSentryWebpackPlugin(
240
+ program: t.Program,
241
+ configObj: t.ObjectExpression,
242
+ options: SourceMapUploadToolConfigurationOptions,
243
+ ) {
244
+ const b = addSentryWebpackPluginImport(program);
245
+
246
+ const sentryPluginCall = b.callExpression(
247
+ b.identifier('sentryWebpackPlugin'),
248
+ [
249
+ b.objectExpression([
250
+ b.objectProperty(
251
+ b.identifier('authToken'),
252
+ b.identifier('process.env.SENTRY_AUTH_TOKEN'),
253
+ ),
254
+ b.objectProperty(b.identifier('org'), b.stringLiteral(options.orgSlug)),
255
+ b.objectProperty(
256
+ b.identifier('project'),
257
+ b.stringLiteral(options.projectSlug),
258
+ ),
259
+ ...(options.selfHosted
260
+ ? [
261
+ b.objectProperty(
262
+ b.identifier('url'),
263
+ b.stringLiteral(options.url),
264
+ ),
265
+ ]
266
+ : []),
267
+ ]),
268
+ ],
269
+ );
270
+
271
+ const pluginsProp = configObj.properties.find(
272
+ (p): p is t.Property =>
273
+ p.type === 'Property' &&
274
+ p.key.type === 'Identifier' &&
275
+ p.key.name === 'plugins',
276
+ );
277
+
278
+ if (pluginsProp) {
279
+ if (pluginsProp.value.type === 'ArrayExpression') {
280
+ pluginsProp.value.elements.push(sentryPluginCall);
281
+ } else {
282
+ pluginsProp.value = b.arrayExpression([sentryPluginCall]);
283
+ }
284
+ return true;
285
+ }
286
+
287
+ configObj.properties.push(
288
+ b.objectProperty(
289
+ b.identifier('plugins'),
290
+ b.arrayExpression([sentryPluginCall]),
291
+ ),
292
+ );
293
+
294
+ return true;
295
+ }
296
+
297
+ function addSentryWebpackPluginImport(program: t.Program) {
298
+ const b = recast.types.builders;
299
+
300
+ const sentryPluginRequireStmt = b.variableDeclaration('const', [
301
+ b.variableDeclarator(
302
+ b.objectPattern([
303
+ b.objectProperty.from({
304
+ key: b.identifier('sentryWebpackPlugin'),
305
+ value: b.identifier('sentryWebpackPlugin'),
306
+ shorthand: true,
307
+ }),
308
+ ]),
309
+ b.callExpression(b.identifier('require'), [
310
+ b.stringLiteral('@sentry/webpack-plugin'),
311
+ ]),
312
+ ),
313
+ ]);
314
+
315
+ program.body.unshift(sentryPluginRequireStmt);
316
+ return b;
317
+ }
318
+
319
+ function enableSourcemapsGeneration(configObj: t.ObjectExpression): boolean {
320
+ const b = recast.types.builders;
321
+
322
+ const devtoolProp = configObj.properties.find(
323
+ (p): p is t.Property =>
324
+ p.type === 'Property' &&
325
+ p.key.type === 'Identifier' &&
326
+ p.key.name === 'devtool',
327
+ );
328
+
329
+ if (devtoolProp) {
330
+ // devtool can have quite a lot of source maps values.
331
+ // see: https://webpack.js.org/configuration/devtool/#devtool
332
+ // For Sentry to work best, we should set it to "source-map" or "hidden-source-map"
333
+ // Heuristic:
334
+ // - all values that contain "hidden" will be set to "hidden-source-map"
335
+ // - all other values will be set to "source-map"
336
+ if (
337
+ (devtoolProp.value.type === 'Literal' ||
338
+ devtoolProp.value.type === 'StringLiteral') &&
339
+ devtoolProp.value.value?.toString().startsWith('hidden-')
340
+ ) {
341
+ devtoolProp.value = b.stringLiteral('hidden-source-map');
342
+ } else {
343
+ devtoolProp.value = b.stringLiteral('source-map');
344
+ }
345
+ return true;
346
+ }
347
+
348
+ configObj.properties.push(
349
+ b.objectProperty(b.identifier('devtool'), b.stringLiteral('source-map')),
350
+ );
351
+
352
+ return true;
353
+ }
354
+
355
+ function getWebpackConfigObject(
356
+ moduleExports: t.AssignmentExpression,
357
+ program: t.Program,
358
+ ): t.ObjectExpression | undefined {
359
+ const rhs = moduleExports.right;
360
+ if (rhs.type === 'ObjectExpression') {
361
+ return rhs;
362
+ }
363
+ if (rhs.type === 'Identifier') {
364
+ const configId = rhs.name;
365
+
366
+ const configDeclaration = program.body.find(
367
+ (s): s is t.VariableDeclaration =>
368
+ s.type === 'VariableDeclaration' &&
369
+ !!s.declarations.find(
370
+ (d) =>
371
+ d.type === 'VariableDeclarator' &&
372
+ d.id.type === 'Identifier' &&
373
+ d.id.name === configId,
374
+ ),
375
+ );
376
+
377
+ const declarator = configDeclaration?.declarations.find(
378
+ (d): d is t.VariableDeclarator =>
379
+ d.type === 'VariableDeclarator' &&
380
+ d.id.type === 'Identifier' &&
381
+ d.id.name === configId,
382
+ );
383
+
384
+ return declarator?.init?.type === 'ObjectExpression'
385
+ ? declarator.init
386
+ : undefined;
387
+ }
388
+
389
+ return undefined;
390
+ }
391
+
392
+ function getCjsModuleExports(
393
+ program: t.Program,
394
+ ): t.AssignmentExpression | undefined {
395
+ const moduleExports = program.body.find(
396
+ (s): s is t.ExpressionStatement =>
397
+ s.type === 'ExpressionStatement' &&
398
+ s.expression.type === 'AssignmentExpression' &&
399
+ s.expression.left.type === 'MemberExpression' &&
400
+ s.expression.left.object.type === 'Identifier' &&
401
+ s.expression.left.object.name === 'module' &&
402
+ s.expression.left.property.type === 'Identifier' &&
403
+ s.expression.left.property.name === 'exports',
404
+ );
405
+ return moduleExports?.expression as t.AssignmentExpression;
406
+ }
@@ -17,6 +17,10 @@ import { abortIfCancelled, isUsingTypeScript } from '../utils/clack-utils';
17
17
  import { debug } from '../utils/debug';
18
18
  import { findFile, hasSentryContent } from '../utils/ast-utils';
19
19
 
20
+ import * as recast from 'recast';
21
+ import x = recast.types;
22
+ import t = x.namedTypes;
23
+
20
24
  const SVELTE_CONFIG_FILE = 'svelte.config.js';
21
25
 
22
26
  export type PartialSvelteConfig = {
@@ -139,7 +143,7 @@ async function mergeHooksFile(
139
143
  dsn: string,
140
144
  ): Promise<void> {
141
145
  const originalHooksMod = await loadFile(hooksFile);
142
- if (hasSentryContent(originalHooksMod)) {
146
+ if (hasSentryContent(originalHooksMod.$ast as t.Program)) {
143
147
  // We don't want to mess with files that already have Sentry content.
144
148
  // Let's just bail out at this point.
145
149
  clack.log.warn(
@@ -399,7 +403,7 @@ async function modifyViteConfig(
399
403
  try {
400
404
  const viteModule = parseModule(viteConfigContent);
401
405
 
402
- if (hasSentryContent(viteModule)) {
406
+ if (hasSentryContent(viteModule.$ast as t.Program)) {
403
407
  clack.log.warn(
404
408
  `File ${chalk.cyan(
405
409
  path.basename(viteConfigPath),
@@ -1,6 +1,8 @@
1
1
  import * as fs from 'fs';
2
- // @ts-ignore - magicast is ESM and TS complains about that. It works though
3
- import { ProxifiedModule } from 'magicast';
2
+
3
+ import * as recast from 'recast';
4
+ import x = recast.types;
5
+ import t = x.namedTypes;
4
6
 
5
7
  /**
6
8
  * Checks if a file where we don't know its concrete file type yet exists
@@ -8,15 +10,29 @@ import { ProxifiedModule } from 'magicast';
8
10
  */
9
11
  export function findFile(
10
12
  filePath: string,
11
- fileTypes: string[] = ['.js', '.ts', '.mjs'],
13
+ fileTypes: string[] = ['.js', '.ts', '.mjs', '.cjs'],
12
14
  ): string | undefined {
13
15
  return fileTypes
14
16
  .map((type) => `${filePath}${type}`)
15
17
  .find((file) => fs.existsSync(file));
16
18
  }
17
19
 
18
- /** Checks if a Sentry package is already mentioned in the file */
19
- export function hasSentryContent(mod: ProxifiedModule<object>): boolean {
20
- const imports = mod.imports.$items.map((i) => i.from);
21
- return !!imports.find((i) => i.startsWith('@sentry/'));
20
+ /**
21
+ * checks for require('@sentry/*') syntax
22
+ */
23
+ export function hasSentryContent(program: t.Program): boolean {
24
+ let foundSentry: boolean | undefined = false;
25
+ recast.visit(program, {
26
+ visitStringLiteral(path) {
27
+ foundSentry = foundSentry || path.node.value.startsWith('@sentry/');
28
+ this.traverse(path);
29
+ },
30
+ visitLiteral(path) {
31
+ foundSentry =
32
+ foundSentry || path.node.value?.toString().startsWith('@sentry/');
33
+ this.traverse(path);
34
+ },
35
+ });
36
+
37
+ return !!foundSentry;
22
38
  }