@sentry/wizard 6.1.2 → 6.2.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 (59) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/e2e-tests/tests/flutter.test.js +16 -2
  3. package/dist/e2e-tests/tests/flutter.test.js.map +1 -1
  4. package/dist/e2e-tests/tests/remix.test.js +3 -2
  5. package/dist/e2e-tests/tests/remix.test.js.map +1 -1
  6. package/dist/lib/Steps/Integrations/Electron.js +4 -0
  7. package/dist/lib/Steps/Integrations/Electron.js.map +1 -1
  8. package/dist/src/apple/apple-wizard.js +10 -0
  9. package/dist/src/apple/apple-wizard.js.map +1 -1
  10. package/dist/src/apple/code-tools.d.ts +1 -1
  11. package/dist/src/apple/code-tools.js +3 -3
  12. package/dist/src/apple/code-tools.js.map +1 -1
  13. package/dist/src/apple/inject-code-snippet.d.ts +2 -1
  14. package/dist/src/apple/inject-code-snippet.js +2 -2
  15. package/dist/src/apple/inject-code-snippet.js.map +1 -1
  16. package/dist/src/apple/templates.d.ts +2 -2
  17. package/dist/src/apple/templates.js +22 -6
  18. package/dist/src/apple/templates.js.map +1 -1
  19. package/dist/src/flutter/code-tools.d.ts +1 -0
  20. package/dist/src/flutter/code-tools.js +6 -0
  21. package/dist/src/flutter/code-tools.js.map +1 -1
  22. package/dist/src/flutter/templates.d.ts +1 -0
  23. package/dist/src/flutter/templates.js +5 -0
  24. package/dist/src/flutter/templates.js.map +1 -1
  25. package/dist/src/nextjs/nextjs-wizard.js +11 -7
  26. package/dist/src/nextjs/nextjs-wizard.js.map +1 -1
  27. package/dist/src/nextjs/utils.d.ts +8 -0
  28. package/dist/src/nextjs/utils.js +36 -1
  29. package/dist/src/nextjs/utils.js.map +1 -1
  30. package/dist/src/react-native/expo-metro.d.ts +2 -2
  31. package/dist/src/react-native/expo-metro.js +32 -27
  32. package/dist/src/react-native/expo-metro.js.map +1 -1
  33. package/dist/src/react-native/metro.d.ts +4 -4
  34. package/dist/src/react-native/metro.js +39 -17
  35. package/dist/src/react-native/metro.js.map +1 -1
  36. package/dist/src/remix/codemods/root.d.ts +1 -0
  37. package/dist/src/remix/codemods/root.js +30 -2
  38. package/dist/src/remix/codemods/root.js.map +1 -1
  39. package/dist/src/version.d.ts +1 -1
  40. package/dist/src/version.js +1 -1
  41. package/dist/src/version.js.map +1 -1
  42. package/dist/test/apple/code-tools.test.js +62 -14
  43. package/dist/test/apple/code-tools.test.js.map +1 -1
  44. package/dist/test/apple/templates.test.js +68 -2
  45. package/dist/test/apple/templates.test.js.map +1 -1
  46. package/dist/test/flutter/code-tools.test.js +1 -0
  47. package/dist/test/flutter/code-tools.test.js.map +1 -1
  48. package/dist/test/flutter/templates.test.js +28 -1
  49. package/dist/test/flutter/templates.test.js.map +1 -1
  50. package/dist/test/nextjs/wizard-double-wrap-prevention.test.d.ts +1 -0
  51. package/dist/test/nextjs/wizard-double-wrap-prevention.test.js +266 -0
  52. package/dist/test/nextjs/wizard-double-wrap-prevention.test.js.map +1 -0
  53. package/dist/test/react-native/expo-metro.test.js +3 -3
  54. package/dist/test/react-native/expo-metro.test.js.map +1 -1
  55. package/dist/test/react-native/metro.test.js +73 -15
  56. package/dist/test/react-native/metro.test.js.map +1 -1
  57. package/dist/test/remix/root.test.js +226 -0
  58. package/dist/test/remix/root.test.js.map +1 -1
  59. package/package.json +1 -9
@@ -1,9 +1,40 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
2
25
  Object.defineProperty(exports, "__esModule", { value: true });
3
26
  // @ts-expect-error - magicast is ESM and TS complains about that. It works though
4
27
  const magicast_1 = require("magicast");
5
28
  const metro_1 = require("../../src/react-native/metro");
6
29
  const vitest_1 = require("vitest");
30
+ const fs = __importStar(require("fs"));
31
+ vitest_1.vi.mock('fs', async () => {
32
+ const actual = await vitest_1.vi.importActual('fs');
33
+ return {
34
+ ...actual,
35
+ existsSync: vitest_1.vi.fn(),
36
+ };
37
+ });
7
38
  (0, vitest_1.describe)('patch metro config - sentry serializer', () => {
8
39
  (0, vitest_1.describe)('patchMetroWithSentryConfigInMemory', () => {
9
40
  (0, vitest_1.it)('patches react native 0.72 default metro config', async () => {
@@ -18,9 +49,7 @@ const vitest_1 = require("vitest");
18
49
  const config = {};
19
50
 
20
51
  module.exports = mergeConfig(getDefaultConfig(__dirname), config);`);
21
- const result = await (0, metro_1.patchMetroWithSentryConfigInMemory)(mod, async () => {
22
- /* noop */
23
- });
52
+ const result = await (0, metro_1.patchMetroWithSentryConfigInMemory)(mod, 'metro.config.js', true);
24
53
  (0, vitest_1.expect)(result).toBe(true);
25
54
  (0, vitest_1.expect)((0, magicast_1.generateCode)(mod.$ast).code)
26
55
  .toBe(`const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');
@@ -57,9 +86,7 @@ module.exports = {
57
86
  }),
58
87
  },
59
88
  };`);
60
- const result = await (0, metro_1.patchMetroWithSentryConfigInMemory)(mod, async () => {
61
- /* noop */
62
- });
89
+ const result = await (0, metro_1.patchMetroWithSentryConfigInMemory)(mod, 'metro.config.js', true);
63
90
  (0, vitest_1.expect)(result).toBe(true);
64
91
  (0, vitest_1.expect)((0, magicast_1.generateCode)(mod.$ast).code).toBe(`const {
65
92
  withSentryConfig
@@ -87,9 +114,7 @@ module.exports = withSentryConfig({
87
114
  const mod = (0, magicast_1.parseModule)(`const testConfig = {};
88
115
 
89
116
  module.exports = testConfig;`);
90
- const result = await (0, metro_1.patchMetroWithSentryConfigInMemory)(mod, async () => {
91
- /* noop */
92
- });
117
+ const result = await (0, metro_1.patchMetroWithSentryConfigInMemory)(mod, 'metro.config.js', true);
93
118
  (0, vitest_1.expect)(result).toBe(true);
94
119
  (0, vitest_1.expect)((0, magicast_1.generateCode)(mod.$ast).code).toBe(`const {
95
120
  withSentryConfig
@@ -151,9 +176,7 @@ const config = {
151
176
  };
152
177
 
153
178
  module.exports = mergeConfig(getDefaultConfig(__dirname), config);`);
154
- const result = await (0, metro_1.patchMetroWithSentryConfigInMemory)(mod, async () => {
155
- /* noop */
156
- });
179
+ const result = await (0, metro_1.patchMetroWithSentryConfigInMemory)(mod, 'metro.config.js', true);
157
180
  (0, vitest_1.expect)(result).toBe(true);
158
181
  (0, vitest_1.expect)((0, magicast_1.generateCode)(mod.$ast).code)
159
182
  .toBe(`const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');
@@ -212,11 +235,21 @@ const config = {
212
235
 
213
236
  module.exports = withSentryConfig(mergeConfig(getDefaultConfig(__dirname), config));`);
214
237
  });
238
+ (0, vitest_1.it)('patches CJS style metro config', async () => {
239
+ const mod = (0, magicast_1.parseModule)(`const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');
240
+
241
+ const config = {};
242
+
243
+ module.exports = mergeConfig(getDefaultConfig(__dirname), config);`);
244
+ const result = await (0, metro_1.patchMetroWithSentryConfigInMemory)(mod, 'metro.config.cjs', true);
245
+ (0, vitest_1.expect)(result).toBe(true);
246
+ const code = (0, magicast_1.generateCode)(mod.$ast).code;
247
+ (0, vitest_1.expect)(code).toContain('require("@sentry/react-native/metro")');
248
+ (0, vitest_1.expect)(code).toContain('withSentryConfig');
249
+ });
215
250
  (0, vitest_1.it)('does not patch react native metro config exported as factory function', async () => {
216
251
  const mod = (0, magicast_1.parseModule)(`module.exports = () => ({});`);
217
- const result = await (0, metro_1.patchMetroWithSentryConfigInMemory)(mod, async () => {
218
- /* noop */
219
- });
252
+ const result = await (0, metro_1.patchMetroWithSentryConfigInMemory)(mod, 'metro.config.js', true);
220
253
  (0, vitest_1.expect)(result).toBe(false);
221
254
  (0, vitest_1.expect)((0, magicast_1.generateCode)(mod.$ast).code).toBe(`module.exports = () => ({});`);
222
255
  });
@@ -331,6 +364,31 @@ module.exports = {
331
364
  });
332
365
  });
333
366
  });
367
+ (0, vitest_1.describe)('Dynamic Metro Config path', () => {
368
+ (0, vitest_1.beforeEach)(() => {
369
+ vitest_1.vi.clearAllMocks();
370
+ });
371
+ (0, vitest_1.it)('finds metro.config.js when it exists', () => {
372
+ vitest_1.vi.mocked(fs.existsSync).mockImplementation((path) => path === 'metro.config.js');
373
+ const result = (0, metro_1.findMetroConfigPath)();
374
+ (0, vitest_1.expect)(result).toBe('metro.config.js');
375
+ });
376
+ (0, vitest_1.it)('finds metro.config.cjs when it exists', () => {
377
+ vitest_1.vi.mocked(fs.existsSync).mockImplementation((path) => path === 'metro.config.cjs');
378
+ const result = (0, metro_1.findMetroConfigPath)();
379
+ (0, vitest_1.expect)(result).toBe('metro.config.cjs');
380
+ });
381
+ (0, vitest_1.it)('prefers metro.config.js over metro.config.cjs when both exist', () => {
382
+ vitest_1.vi.mocked(fs.existsSync).mockImplementation(() => true);
383
+ const result = (0, metro_1.findMetroConfigPath)();
384
+ (0, vitest_1.expect)(result).toBe('metro.config.js');
385
+ });
386
+ (0, vitest_1.it)('returns undefined when no metro config exists', () => {
387
+ vitest_1.vi.mocked(fs.existsSync).mockImplementation(() => false);
388
+ const result = (0, metro_1.findMetroConfigPath)();
389
+ (0, vitest_1.expect)(result).toBeUndefined();
390
+ });
391
+ });
334
392
  function getModuleExportsObject(mod, index = 0) {
335
393
  return mod.$ast.body[index]
336
394
  .expression.right;
@@ -1 +1 @@
1
- {"version":3,"file":"metro.test.js","sourceRoot":"","sources":["../../../test/react-native/metro.test.ts"],"names":[],"mappings":"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","sourcesContent":["// @ts-expect-error - magicast is ESM and TS complains about that. It works though\nimport { generateCode, type ProxifiedModule, parseModule } from 'magicast';\n\nimport * as recast from 'recast';\nimport x = recast.types;\nimport t = x.namedTypes;\n\nimport {\n addSentrySerializerRequireToMetroConfig,\n addSentrySerializerToMetroConfig,\n getMetroConfigObject,\n patchMetroWithSentryConfigInMemory,\n} from '../../src/react-native/metro';\nimport { describe, expect, it } from 'vitest';\n\ndescribe('patch metro config - sentry serializer', () => {\n describe('patchMetroWithSentryConfigInMemory', () => {\n it('patches react native 0.72 default metro config', async () => {\n const mod =\n parseModule(`const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');\n\n/**\n * Metro configuration\n * https://reactnative.dev/docs/metro\n *\n * @type {import('metro-config').MetroConfig}\n */\nconst config = {};\n\nmodule.exports = mergeConfig(getDefaultConfig(__dirname), config);`);\n\n const result = await patchMetroWithSentryConfigInMemory(mod, async () => {\n /* noop */\n });\n expect(result).toBe(true);\n expect(generateCode(mod.$ast).code)\n .toBe(`const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');\n\nconst {\n withSentryConfig\n} = require(\"@sentry/react-native/metro\");\n\n/**\n * Metro configuration\n * https://reactnative.dev/docs/metro\n *\n * @type {import('metro-config').MetroConfig}\n */\nconst config = {};\n\nmodule.exports = withSentryConfig(mergeConfig(getDefaultConfig(__dirname), config));`);\n });\n\n it('patches react native 0.65 default metro config', async () => {\n const mod = parseModule(`/**\n* Metro configuration for React Native\n* https://github.com/facebook/react-native\n*\n* @format\n*/\n\nmodule.exports = {\n transformer: {\n getTransformOptions: async () => ({\n transform: {\n experimentalImportSupport: false,\n inlineRequires: true,\n },\n }),\n },\n};`);\n\n const result = await patchMetroWithSentryConfigInMemory(mod, async () => {\n /* noop */\n });\n expect(result).toBe(true);\n expect(generateCode(mod.$ast).code).toBe(`const {\n withSentryConfig\n} = require(\"@sentry/react-native/metro\");\n\n/**\n* Metro configuration for React Native\n* https://github.com/facebook/react-native\n*\n* @format\n*/\n\nmodule.exports = withSentryConfig({\n transformer: {\n getTransformOptions: async () => ({\n transform: {\n experimentalImportSupport: false,\n inlineRequires: true,\n },\n }),\n },\n});`);\n });\n\n it('patches react native metro config exported variable', async () => {\n const mod = parseModule(`const testConfig = {};\n\nmodule.exports = testConfig;`);\n\n const result = await patchMetroWithSentryConfigInMemory(mod, async () => {\n /* noop */\n });\n expect(result).toBe(true);\n expect(generateCode(mod.$ast).code).toBe(`const {\n withSentryConfig\n} = require(\"@sentry/react-native/metro\");\n\nconst testConfig = {};\n\nmodule.exports = withSentryConfig(testConfig);`);\n });\n\n it('patches custom react native metro config', async () => {\n const mod =\n parseModule(`const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');\n\nconst defaultConfig = getDefaultConfig(__dirname);\nconst {assetExts, sourceExts} = defaultConfig.resolver;\n/**\n * Metro configuration\n * https://facebook.github.io/metro/docs/configuration\n *\n * @type {import('metro-config').MetroConfig}\n */\n\nconst jsoMetroPlugin = require('obfuscator-io-metro-plugin')(\n {\n // for these option look javascript-obfuscator library options from above url\n compact: false,\n sourceMap: false,\n controlFlowFlattening: true,\n controlFlowFlatteningThreshold: 1,\n numbersToExpressions: true,\n simplify: true,\n stringArrayShuffle: true,\n splitStrings: true,\n stringArrayThreshold: 1,\n },\n {\n runInDev: false /* optional */,\n logObfuscatedFiles: true /* optional generated files will be located at ./.jso */,\n // source Map generated after obfuscation is not useful right now\n sourceMapLocation:\n './index.android.bundle.map' /* optional only works if sourceMap: true in obfuscation option */,\n },\n);\n\nconst config = {\n transformer: {\n babelTransformerPath: require.resolve('react-native-svg-transformer'),\n getTransformOptions: async () => ({\n transform: {\n experimentalImportSupport: false,\n inlineRequires: true,\n },\n }),\n },\n resolver: {\n assetExts: assetExts.filter(ext => ext !== 'svg'),\n sourceExts: [...sourceExts, 'svg'],\n },\n ...jsoMetroPlugin,\n};\n\nmodule.exports = mergeConfig(getDefaultConfig(__dirname), config);`);\n\n const result = await patchMetroWithSentryConfigInMemory(mod, async () => {\n /* noop */\n });\n expect(result).toBe(true);\n expect(generateCode(mod.$ast).code)\n .toBe(`const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');\n\nconst {\n withSentryConfig\n} = require(\"@sentry/react-native/metro\");\n\nconst defaultConfig = getDefaultConfig(__dirname);\nconst {assetExts, sourceExts} = defaultConfig.resolver;\n/**\n * Metro configuration\n * https://facebook.github.io/metro/docs/configuration\n *\n * @type {import('metro-config').MetroConfig}\n */\n\nconst jsoMetroPlugin = require('obfuscator-io-metro-plugin')(\n {\n // for these option look javascript-obfuscator library options from above url\n compact: false,\n sourceMap: false,\n controlFlowFlattening: true,\n controlFlowFlatteningThreshold: 1,\n numbersToExpressions: true,\n simplify: true,\n stringArrayShuffle: true,\n splitStrings: true,\n stringArrayThreshold: 1,\n },\n {\n runInDev: false /* optional */,\n logObfuscatedFiles: true /* optional generated files will be located at ./.jso */,\n // source Map generated after obfuscation is not useful right now\n sourceMapLocation:\n './index.android.bundle.map' /* optional only works if sourceMap: true in obfuscation option */,\n },\n);\n\nconst config = {\n transformer: {\n babelTransformerPath: require.resolve('react-native-svg-transformer'),\n getTransformOptions: async () => ({\n transform: {\n experimentalImportSupport: false,\n inlineRequires: true,\n },\n }),\n },\n resolver: {\n assetExts: assetExts.filter(ext => ext !== 'svg'),\n sourceExts: [...sourceExts, 'svg'],\n },\n ...jsoMetroPlugin,\n};\n\nmodule.exports = withSentryConfig(mergeConfig(getDefaultConfig(__dirname), config));`);\n });\n\n it('does not patch react native metro config exported as factory function', async () => {\n const mod = parseModule(`module.exports = () => ({});`);\n\n const result = await patchMetroWithSentryConfigInMemory(mod, async () => {\n /* noop */\n });\n expect(result).toBe(false);\n expect(generateCode(mod.$ast).code).toBe(`module.exports = () => ({});`);\n });\n });\n\n describe('addSentrySerializerToMetroConfig', () => {\n it('add to empty config', () => {\n const mod = parseModule(`module.exports = {\n other: 'config'\n }`);\n const configObject = getModuleExportsObject(mod);\n const result = addSentrySerializerToMetroConfig(configObject);\n expect(result).toBe(true);\n expect(generateCode(mod.$ast).code).toBe(`module.exports = {\n other: 'config',\n\n serializer: {\n customSerializer: createSentryMetroSerializer()\n }\n}`);\n });\n\n it('add to existing serializer config', () => {\n const mod = parseModule(`module.exports = {\n other: 'config',\n serializer: {\n other: 'config'\n }\n}`);\n const configObject = getModuleExportsObject(mod);\n const result = addSentrySerializerToMetroConfig(configObject);\n expect(result).toBe(true);\n expect(generateCode(mod.$ast).code).toBe(`module.exports = {\n other: 'config',\n serializer: {\n other: 'config',\n customSerializer: createSentryMetroSerializer()\n }\n}`);\n });\n\n it('not add to existing customSerializer config', () => {\n const mod = parseModule(`module.exports = {\n other: 'config',\n serializer: {\n other: 'config',\n customSerializer: 'existing-serializer'\n }\n}`);\n const configObject = getModuleExportsObject(mod);\n const result = addSentrySerializerToMetroConfig(configObject);\n expect(result).toBe(false);\n expect(generateCode(mod.$ast).code).toBe(`module.exports = {\n other: 'config',\n serializer: {\n other: 'config',\n customSerializer: 'existing-serializer'\n }\n}`);\n });\n });\n\n describe('addSentrySerializerImportToMetroConfig', () => {\n it('add import', () => {\n const mod =\n parseModule(`const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');\n\nmodule.exports = {\n other: 'config'\n}`);\n const result = addSentrySerializerRequireToMetroConfig(\n mod.$ast as t.Program,\n );\n expect(result).toBe(true);\n expect(generateCode(mod.$ast).code)\n .toBe(`const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');\n\nconst {\n createSentryMetroSerializer\n} = require(\"@sentry/react-native/dist/js/tools/sentryMetroSerializer\");\n\nmodule.exports = {\n other: 'config'\n}`);\n });\n });\n\n describe('getMetroConfigObject', () => {\n it('get config object from variable called config', () => {\n const mod = parseModule(`var config = { some: 'config' };`);\n const configObject = getMetroConfigObject(mod.$ast as t.Program);\n expect(\n ((configObject?.properties[0] as t.ObjectProperty).key as t.Identifier)\n .name,\n ).toBe('some');\n expect(\n (\n (configObject?.properties[0] as t.ObjectProperty)\n .value as t.StringLiteral\n ).value,\n ).toBe('config');\n });\n\n it('get config object from const called config', () => {\n const mod = parseModule(`const config = { some: 'config' };`);\n const configObject = getMetroConfigObject(mod.$ast as t.Program);\n expect(\n ((configObject?.properties[0] as t.ObjectProperty).key as t.Identifier)\n .name,\n ).toBe('some');\n expect(\n (\n (configObject?.properties[0] as t.ObjectProperty)\n .value as t.StringLiteral\n ).value,\n ).toBe('config');\n });\n\n it('get config oject from let called config', () => {\n const mod = parseModule(`let config = { some: 'config' };`);\n const configObject = getMetroConfigObject(mod.$ast as t.Program);\n expect(\n ((configObject?.properties[0] as t.ObjectProperty).key as t.Identifier)\n .name,\n ).toBe('some');\n expect(\n (\n (configObject?.properties[0] as t.ObjectProperty)\n .value as t.StringLiteral\n ).value,\n ).toBe('config');\n });\n\n it('get config object from module.exports', () => {\n const mod = parseModule(`module.exports = { some: 'config' };`);\n const configObject = getMetroConfigObject(mod.$ast as t.Program);\n expect(\n ((configObject?.properties[0] as t.ObjectProperty).key as t.Identifier)\n .name,\n ).toBe('some');\n expect(\n (\n (configObject?.properties[0] as t.ObjectProperty)\n .value as t.StringLiteral\n ).value,\n ).toBe('config');\n });\n });\n});\n\nfunction getModuleExportsObject(\n mod: ProxifiedModule,\n index = 0,\n): t.ObjectExpression {\n return (\n ((mod.$ast as t.Program).body[index] as t.ExpressionStatement)\n .expression as t.AssignmentExpression\n ).right as t.ObjectExpression;\n}\n"]}
1
+ {"version":3,"file":"metro.test.js","sourceRoot":"","sources":["../../../test/react-native/metro.test.ts"],"names":[],"mappings":"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","sourcesContent":["// @ts-expect-error - magicast is ESM and TS complains about that. It works though\nimport { generateCode, type ProxifiedModule, parseModule } from 'magicast';\n\nimport * as recast from 'recast';\nimport x = recast.types;\nimport t = x.namedTypes;\n\nimport {\n addSentrySerializerRequireToMetroConfig,\n addSentrySerializerToMetroConfig,\n findMetroConfigPath,\n getMetroConfigObject,\n patchMetroWithSentryConfigInMemory,\n} from '../../src/react-native/metro';\nimport { beforeEach, describe, expect, it, vi } from 'vitest';\nimport * as fs from 'fs';\n\nvi.mock('fs', async () => {\n const actual = await vi.importActual('fs');\n return {\n ...actual,\n existsSync: vi.fn(),\n };\n});\n\ndescribe('patch metro config - sentry serializer', () => {\n describe('patchMetroWithSentryConfigInMemory', () => {\n it('patches react native 0.72 default metro config', async () => {\n const mod =\n parseModule(`const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');\n\n/**\n * Metro configuration\n * https://reactnative.dev/docs/metro\n *\n * @type {import('metro-config').MetroConfig}\n */\nconst config = {};\n\nmodule.exports = mergeConfig(getDefaultConfig(__dirname), config);`);\n\n const result = await patchMetroWithSentryConfigInMemory(\n mod,\n 'metro.config.js',\n true,\n );\n expect(result).toBe(true);\n expect(generateCode(mod.$ast).code)\n .toBe(`const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');\n\nconst {\n withSentryConfig\n} = require(\"@sentry/react-native/metro\");\n\n/**\n * Metro configuration\n * https://reactnative.dev/docs/metro\n *\n * @type {import('metro-config').MetroConfig}\n */\nconst config = {};\n\nmodule.exports = withSentryConfig(mergeConfig(getDefaultConfig(__dirname), config));`);\n });\n\n it('patches react native 0.65 default metro config', async () => {\n const mod = parseModule(`/**\n* Metro configuration for React Native\n* https://github.com/facebook/react-native\n*\n* @format\n*/\n\nmodule.exports = {\n transformer: {\n getTransformOptions: async () => ({\n transform: {\n experimentalImportSupport: false,\n inlineRequires: true,\n },\n }),\n },\n};`);\n\n const result = await patchMetroWithSentryConfigInMemory(\n mod,\n 'metro.config.js',\n true,\n );\n expect(result).toBe(true);\n expect(generateCode(mod.$ast).code).toBe(`const {\n withSentryConfig\n} = require(\"@sentry/react-native/metro\");\n\n/**\n* Metro configuration for React Native\n* https://github.com/facebook/react-native\n*\n* @format\n*/\n\nmodule.exports = withSentryConfig({\n transformer: {\n getTransformOptions: async () => ({\n transform: {\n experimentalImportSupport: false,\n inlineRequires: true,\n },\n }),\n },\n});`);\n });\n\n it('patches react native metro config exported variable', async () => {\n const mod = parseModule(`const testConfig = {};\n\nmodule.exports = testConfig;`);\n\n const result = await patchMetroWithSentryConfigInMemory(\n mod,\n 'metro.config.js',\n true,\n );\n expect(result).toBe(true);\n expect(generateCode(mod.$ast).code).toBe(`const {\n withSentryConfig\n} = require(\"@sentry/react-native/metro\");\n\nconst testConfig = {};\n\nmodule.exports = withSentryConfig(testConfig);`);\n });\n\n it('patches custom react native metro config', async () => {\n const mod =\n parseModule(`const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');\n\nconst defaultConfig = getDefaultConfig(__dirname);\nconst {assetExts, sourceExts} = defaultConfig.resolver;\n/**\n * Metro configuration\n * https://facebook.github.io/metro/docs/configuration\n *\n * @type {import('metro-config').MetroConfig}\n */\n\nconst jsoMetroPlugin = require('obfuscator-io-metro-plugin')(\n {\n // for these option look javascript-obfuscator library options from above url\n compact: false,\n sourceMap: false,\n controlFlowFlattening: true,\n controlFlowFlatteningThreshold: 1,\n numbersToExpressions: true,\n simplify: true,\n stringArrayShuffle: true,\n splitStrings: true,\n stringArrayThreshold: 1,\n },\n {\n runInDev: false /* optional */,\n logObfuscatedFiles: true /* optional generated files will be located at ./.jso */,\n // source Map generated after obfuscation is not useful right now\n sourceMapLocation:\n './index.android.bundle.map' /* optional only works if sourceMap: true in obfuscation option */,\n },\n);\n\nconst config = {\n transformer: {\n babelTransformerPath: require.resolve('react-native-svg-transformer'),\n getTransformOptions: async () => ({\n transform: {\n experimentalImportSupport: false,\n inlineRequires: true,\n },\n }),\n },\n resolver: {\n assetExts: assetExts.filter(ext => ext !== 'svg'),\n sourceExts: [...sourceExts, 'svg'],\n },\n ...jsoMetroPlugin,\n};\n\nmodule.exports = mergeConfig(getDefaultConfig(__dirname), config);`);\n\n const result = await patchMetroWithSentryConfigInMemory(\n mod,\n 'metro.config.js',\n true,\n );\n expect(result).toBe(true);\n expect(generateCode(mod.$ast).code)\n .toBe(`const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');\n\nconst {\n withSentryConfig\n} = require(\"@sentry/react-native/metro\");\n\nconst defaultConfig = getDefaultConfig(__dirname);\nconst {assetExts, sourceExts} = defaultConfig.resolver;\n/**\n * Metro configuration\n * https://facebook.github.io/metro/docs/configuration\n *\n * @type {import('metro-config').MetroConfig}\n */\n\nconst jsoMetroPlugin = require('obfuscator-io-metro-plugin')(\n {\n // for these option look javascript-obfuscator library options from above url\n compact: false,\n sourceMap: false,\n controlFlowFlattening: true,\n controlFlowFlatteningThreshold: 1,\n numbersToExpressions: true,\n simplify: true,\n stringArrayShuffle: true,\n splitStrings: true,\n stringArrayThreshold: 1,\n },\n {\n runInDev: false /* optional */,\n logObfuscatedFiles: true /* optional generated files will be located at ./.jso */,\n // source Map generated after obfuscation is not useful right now\n sourceMapLocation:\n './index.android.bundle.map' /* optional only works if sourceMap: true in obfuscation option */,\n },\n);\n\nconst config = {\n transformer: {\n babelTransformerPath: require.resolve('react-native-svg-transformer'),\n getTransformOptions: async () => ({\n transform: {\n experimentalImportSupport: false,\n inlineRequires: true,\n },\n }),\n },\n resolver: {\n assetExts: assetExts.filter(ext => ext !== 'svg'),\n sourceExts: [...sourceExts, 'svg'],\n },\n ...jsoMetroPlugin,\n};\n\nmodule.exports = withSentryConfig(mergeConfig(getDefaultConfig(__dirname), config));`);\n });\n\n it('patches CJS style metro config', async () => {\n const mod =\n parseModule(`const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');\n\nconst config = {};\n\nmodule.exports = mergeConfig(getDefaultConfig(__dirname), config);`);\n\n const result = await patchMetroWithSentryConfigInMemory(\n mod,\n 'metro.config.cjs',\n true,\n );\n expect(result).toBe(true);\n\n const code = generateCode(mod.$ast).code;\n expect(code).toContain('require(\"@sentry/react-native/metro\")');\n expect(code).toContain('withSentryConfig');\n });\n\n it('does not patch react native metro config exported as factory function', async () => {\n const mod = parseModule(`module.exports = () => ({});`);\n\n const result = await patchMetroWithSentryConfigInMemory(\n mod,\n 'metro.config.js',\n true,\n );\n expect(result).toBe(false);\n expect(generateCode(mod.$ast).code).toBe(`module.exports = () => ({});`);\n });\n });\n\n describe('addSentrySerializerToMetroConfig', () => {\n it('add to empty config', () => {\n const mod = parseModule(`module.exports = {\n other: 'config'\n }`);\n const configObject = getModuleExportsObject(mod);\n const result = addSentrySerializerToMetroConfig(configObject);\n expect(result).toBe(true);\n expect(generateCode(mod.$ast).code).toBe(`module.exports = {\n other: 'config',\n\n serializer: {\n customSerializer: createSentryMetroSerializer()\n }\n}`);\n });\n\n it('add to existing serializer config', () => {\n const mod = parseModule(`module.exports = {\n other: 'config',\n serializer: {\n other: 'config'\n }\n}`);\n const configObject = getModuleExportsObject(mod);\n const result = addSentrySerializerToMetroConfig(configObject);\n expect(result).toBe(true);\n expect(generateCode(mod.$ast).code).toBe(`module.exports = {\n other: 'config',\n serializer: {\n other: 'config',\n customSerializer: createSentryMetroSerializer()\n }\n}`);\n });\n\n it('not add to existing customSerializer config', () => {\n const mod = parseModule(`module.exports = {\n other: 'config',\n serializer: {\n other: 'config',\n customSerializer: 'existing-serializer'\n }\n}`);\n const configObject = getModuleExportsObject(mod);\n const result = addSentrySerializerToMetroConfig(configObject);\n expect(result).toBe(false);\n expect(generateCode(mod.$ast).code).toBe(`module.exports = {\n other: 'config',\n serializer: {\n other: 'config',\n customSerializer: 'existing-serializer'\n }\n}`);\n });\n });\n\n describe('addSentrySerializerImportToMetroConfig', () => {\n it('add import', () => {\n const mod =\n parseModule(`const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');\n\nmodule.exports = {\n other: 'config'\n}`);\n const result = addSentrySerializerRequireToMetroConfig(\n mod.$ast as t.Program,\n );\n expect(result).toBe(true);\n expect(generateCode(mod.$ast).code)\n .toBe(`const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');\n\nconst {\n createSentryMetroSerializer\n} = require(\"@sentry/react-native/dist/js/tools/sentryMetroSerializer\");\n\nmodule.exports = {\n other: 'config'\n}`);\n });\n });\n\n describe('getMetroConfigObject', () => {\n it('get config object from variable called config', () => {\n const mod = parseModule(`var config = { some: 'config' };`);\n const configObject = getMetroConfigObject(mod.$ast as t.Program);\n expect(\n ((configObject?.properties[0] as t.ObjectProperty).key as t.Identifier)\n .name,\n ).toBe('some');\n expect(\n (\n (configObject?.properties[0] as t.ObjectProperty)\n .value as t.StringLiteral\n ).value,\n ).toBe('config');\n });\n\n it('get config object from const called config', () => {\n const mod = parseModule(`const config = { some: 'config' };`);\n const configObject = getMetroConfigObject(mod.$ast as t.Program);\n expect(\n ((configObject?.properties[0] as t.ObjectProperty).key as t.Identifier)\n .name,\n ).toBe('some');\n expect(\n (\n (configObject?.properties[0] as t.ObjectProperty)\n .value as t.StringLiteral\n ).value,\n ).toBe('config');\n });\n\n it('get config oject from let called config', () => {\n const mod = parseModule(`let config = { some: 'config' };`);\n const configObject = getMetroConfigObject(mod.$ast as t.Program);\n expect(\n ((configObject?.properties[0] as t.ObjectProperty).key as t.Identifier)\n .name,\n ).toBe('some');\n expect(\n (\n (configObject?.properties[0] as t.ObjectProperty)\n .value as t.StringLiteral\n ).value,\n ).toBe('config');\n });\n\n it('get config object from module.exports', () => {\n const mod = parseModule(`module.exports = { some: 'config' };`);\n const configObject = getMetroConfigObject(mod.$ast as t.Program);\n expect(\n ((configObject?.properties[0] as t.ObjectProperty).key as t.Identifier)\n .name,\n ).toBe('some');\n expect(\n (\n (configObject?.properties[0] as t.ObjectProperty)\n .value as t.StringLiteral\n ).value,\n ).toBe('config');\n });\n });\n});\n\ndescribe('Dynamic Metro Config path', () => {\n beforeEach(() => {\n vi.clearAllMocks();\n });\n\n it('finds metro.config.js when it exists', () => {\n vi.mocked(fs.existsSync).mockImplementation(\n (path: string) => path === 'metro.config.js',\n );\n\n const result = findMetroConfigPath();\n expect(result).toBe('metro.config.js');\n });\n\n it('finds metro.config.cjs when it exists', () => {\n vi.mocked(fs.existsSync).mockImplementation(\n (path: string) => path === 'metro.config.cjs',\n );\n\n const result = findMetroConfigPath();\n expect(result).toBe('metro.config.cjs');\n });\n\n it('prefers metro.config.js over metro.config.cjs when both exist', () => {\n vi.mocked(fs.existsSync).mockImplementation(() => true);\n\n const result = findMetroConfigPath();\n expect(result).toBe('metro.config.js');\n });\n\n it('returns undefined when no metro config exists', () => {\n vi.mocked(fs.existsSync).mockImplementation(() => false);\n\n const result = findMetroConfigPath();\n expect(result).toBeUndefined();\n });\n});\n\nfunction getModuleExportsObject(\n mod: ProxifiedModule,\n index = 0,\n): t.ObjectExpression {\n return (\n ((mod.$ast as t.Program).body[index] as t.ExpressionStatement)\n .expression as t.AssignmentExpression\n ).right as t.ObjectExpression;\n}\n"]}
@@ -1,9 +1,47 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
2
25
  Object.defineProperty(exports, "__esModule", { value: true });
3
26
  // @ts-expect-error - magicast is ESM and TS complains about that. It works though
4
27
  const magicast_1 = require("magicast");
5
28
  const vitest_1 = require("vitest");
29
+ const fs = __importStar(require("fs"));
30
+ const path = __importStar(require("path"));
6
31
  const root_1 = require("../../src/remix/codemods/root");
32
+ vitest_1.vi.mock('@clack/prompts', () => {
33
+ const mock = {
34
+ log: {
35
+ warn: vitest_1.vi.fn(),
36
+ info: vitest_1.vi.fn(),
37
+ success: vitest_1.vi.fn(),
38
+ },
39
+ };
40
+ return {
41
+ default: mock,
42
+ ...mock,
43
+ };
44
+ });
7
45
  (0, vitest_1.describe)('wrapAppWithSentry', () => {
8
46
  (0, vitest_1.it)('should wrap the app with Sentry', () => {
9
47
  // Empty root.tsx file for testing
@@ -28,4 +66,192 @@ const root_1 = require("../../src/remix/codemods/root");
28
66
  `);
29
67
  });
30
68
  });
69
+ (0, vitest_1.describe)('isWithSentryAlreadyUsed', () => {
70
+ (0, vitest_1.it)('should return false when withSentry is not used', () => {
71
+ const rootAst = (0, magicast_1.parseModule)(`
72
+ import { Outlet } from '@remix-run/react';
73
+
74
+ export default function App() {
75
+ return <Outlet />;
76
+ }
77
+ `);
78
+ (0, vitest_1.expect)((0, root_1.isWithSentryAlreadyUsed)(rootAst)).toBe(false);
79
+ });
80
+ (0, vitest_1.it)('should return true when withSentry is used in default export', () => {
81
+ const rootAst = (0, magicast_1.parseModule)(`
82
+ import { withSentry } from '@sentry/remix';
83
+ import { Outlet } from '@remix-run/react';
84
+
85
+ function App() {
86
+ return <Outlet />;
87
+ }
88
+
89
+ export default withSentry(App);
90
+ `);
91
+ (0, vitest_1.expect)((0, root_1.isWithSentryAlreadyUsed)(rootAst)).toBe(true);
92
+ });
93
+ (0, vitest_1.it)('should return true when withSentry is used in a variable assignment', () => {
94
+ const rootAst = (0, magicast_1.parseModule)(`
95
+ import { withSentry } from '@sentry/remix';
96
+ import { Outlet } from '@remix-run/react';
97
+
98
+ function App() {
99
+ return <Outlet />;
100
+ }
101
+
102
+ const WrappedApp = withSentry(App);
103
+ export default WrappedApp;
104
+ `);
105
+ (0, vitest_1.expect)((0, root_1.isWithSentryAlreadyUsed)(rootAst)).toBe(true);
106
+ });
107
+ (0, vitest_1.it)('should return true when withSentry is used inside a function', () => {
108
+ const rootAst = (0, magicast_1.parseModule)(`
109
+ import { withSentry } from '@sentry/remix';
110
+ import { Outlet } from '@remix-run/react';
111
+
112
+ function App() {
113
+ return <Outlet />;
114
+ }
115
+
116
+ function createApp() {
117
+ return withSentry(App);
118
+ }
119
+
120
+ export default createApp();
121
+ `);
122
+ (0, vitest_1.expect)((0, root_1.isWithSentryAlreadyUsed)(rootAst)).toBe(true);
123
+ });
124
+ (0, vitest_1.it)('should return false when withSentry is imported but not used', () => {
125
+ const rootAst = (0, magicast_1.parseModule)(`
126
+ import { withSentry } from '@sentry/remix';
127
+ import { Outlet } from '@remix-run/react';
128
+
129
+ export default function App() {
130
+ return <Outlet />;
131
+ }
132
+ `);
133
+ (0, vitest_1.expect)((0, root_1.isWithSentryAlreadyUsed)(rootAst)).toBe(false);
134
+ });
135
+ (0, vitest_1.it)('should return false when a different function with similar name is used', () => {
136
+ const rootAst = (0, magicast_1.parseModule)(`
137
+ import { Outlet } from '@remix-run/react';
138
+
139
+ function withSentryLike() {
140
+ return null;
141
+ }
142
+
143
+ export default function App() {
144
+ withSentryLike();
145
+ return <Outlet />;
146
+ }
147
+ `);
148
+ (0, vitest_1.expect)((0, root_1.isWithSentryAlreadyUsed)(rootAst)).toBe(false);
149
+ });
150
+ });
151
+ (0, vitest_1.describe)('instrumentRoot', () => {
152
+ const fixturesDir = path.join(__dirname, 'fixtures');
153
+ const tmpDir = path.join(fixturesDir, 'tmp');
154
+ (0, vitest_1.beforeEach)(() => {
155
+ vitest_1.vi.clearAllMocks();
156
+ // Mock process.cwd() to return the fixtures directory
157
+ vitest_1.vi.spyOn(process, 'cwd').mockReturnValue(fixturesDir);
158
+ });
159
+ (0, vitest_1.afterEach)(() => {
160
+ // Clean up any temporary files
161
+ if (fs.existsSync(tmpDir)) {
162
+ try {
163
+ // Remove files first, then directory
164
+ const appDir = path.join(tmpDir, 'app');
165
+ if (fs.existsSync(appDir)) {
166
+ const files = fs.readdirSync(appDir);
167
+ files.forEach((file) => {
168
+ fs.unlinkSync(path.join(appDir, file));
169
+ });
170
+ fs.rmdirSync(appDir);
171
+ }
172
+ const files = fs.readdirSync(tmpDir);
173
+ files.forEach((file) => {
174
+ const filePath = path.join(tmpDir, file);
175
+ const stat = fs.statSync(filePath);
176
+ if (stat.isDirectory()) {
177
+ fs.rmdirSync(filePath);
178
+ }
179
+ else {
180
+ fs.unlinkSync(filePath);
181
+ }
182
+ });
183
+ }
184
+ catch (error) {
185
+ // Ignore cleanup errors in tests
186
+ }
187
+ }
188
+ });
189
+ (0, vitest_1.it)('should add ErrorBoundary and wrap app with Sentry when no ErrorBoundary exists and withSentry is not used', async () => {
190
+ // Copy fixture to tmp directory for testing
191
+ const srcFile = path.join(fixturesDir, 'root-no-error-boundary.tsx');
192
+ const appDir = path.join(tmpDir, 'app');
193
+ // Create app directory and copy file
194
+ fs.mkdirSync(appDir, { recursive: true });
195
+ fs.copyFileSync(srcFile, path.join(appDir, 'root.tsx'));
196
+ // Mock process.cwd() to return tmpDir
197
+ vitest_1.vi.spyOn(process, 'cwd').mockReturnValue(tmpDir);
198
+ await (0, root_1.instrumentRoot)('root.tsx');
199
+ // Check that the file was modified correctly
200
+ const modifiedContent = fs.readFileSync(path.join(appDir, 'root.tsx'), 'utf8');
201
+ (0, vitest_1.expect)(modifiedContent).toContain('import { captureRemixErrorBoundaryError, withSentry } from "@sentry/remix";');
202
+ (0, vitest_1.expect)(modifiedContent).toContain("import { Outlet, useRouteError } from '@remix-run/react';");
203
+ (0, vitest_1.expect)(modifiedContent).toContain('withSentry(App)');
204
+ (0, vitest_1.expect)(modifiedContent).toContain('const ErrorBoundary = () => {');
205
+ });
206
+ (0, vitest_1.it)('should wrap app with Sentry when ErrorBoundary exists but no Sentry content', async () => {
207
+ const srcFile = path.join(fixturesDir, 'root-with-error-boundary.tsx');
208
+ const appDir = path.join(tmpDir, 'app');
209
+ fs.mkdirSync(appDir, { recursive: true });
210
+ fs.copyFileSync(srcFile, path.join(appDir, 'root.tsx'));
211
+ vitest_1.vi.spyOn(process, 'cwd').mockReturnValue(tmpDir);
212
+ await (0, root_1.instrumentRoot)('root.tsx');
213
+ const modifiedContent = fs.readFileSync(path.join(appDir, 'root.tsx'), 'utf8');
214
+ (0, vitest_1.expect)(modifiedContent).toContain('import { captureRemixErrorBoundaryError, withSentry } from "@sentry/remix";');
215
+ (0, vitest_1.expect)(modifiedContent).toContain('withSentry(App)');
216
+ });
217
+ (0, vitest_1.it)('should wrap app with Sentry when ErrorBoundary exists with Sentry content but withSentry is not used', async () => {
218
+ const srcFile = path.join(fixturesDir, 'root-with-sentry-error-boundary.tsx');
219
+ const appDir = path.join(tmpDir, 'app');
220
+ fs.mkdirSync(appDir, { recursive: true });
221
+ fs.copyFileSync(srcFile, path.join(appDir, 'root.tsx'));
222
+ vitest_1.vi.spyOn(process, 'cwd').mockReturnValue(tmpDir);
223
+ await (0, root_1.instrumentRoot)('root.tsx');
224
+ const modifiedContent = fs.readFileSync(path.join(appDir, 'root.tsx'), 'utf8');
225
+ (0, vitest_1.expect)(modifiedContent).toContain("import { captureRemixErrorBoundaryError, withSentry } from '@sentry/remix';");
226
+ (0, vitest_1.expect)(modifiedContent).toContain('withSentry(App)');
227
+ });
228
+ (0, vitest_1.it)('should not wrap app when withSentry is already used', async () => {
229
+ const srcFile = path.join(fixturesDir, 'root-already-wrapped.tsx');
230
+ const appDir = path.join(tmpDir, 'app');
231
+ fs.mkdirSync(appDir, { recursive: true });
232
+ fs.copyFileSync(srcFile, path.join(appDir, 'root.tsx'));
233
+ vitest_1.vi.spyOn(process, 'cwd').mockReturnValue(tmpDir);
234
+ await (0, root_1.instrumentRoot)('root.tsx');
235
+ const modifiedContent = fs.readFileSync(path.join(appDir, 'root.tsx'), 'utf8');
236
+ // Should not add withSentry import or wrap again
237
+ (0, vitest_1.expect)(modifiedContent).toContain('withSentry(App)');
238
+ // Count occurrences of withSentry to ensure it's not duplicated
239
+ const withSentryOccurrences = (modifiedContent.match(/withSentry/g) || [])
240
+ .length;
241
+ (0, vitest_1.expect)(withSentryOccurrences).toBe(2); // One import, one usage
242
+ // The content should remain largely the same since withSentry is already used
243
+ (0, vitest_1.expect)(modifiedContent).toContain('export default withSentry(App)');
244
+ });
245
+ (0, vitest_1.it)('should handle ErrorBoundary as variable declaration', async () => {
246
+ const srcFile = path.join(fixturesDir, 'root-variable-error-boundary.tsx');
247
+ const appDir = path.join(tmpDir, 'app');
248
+ fs.mkdirSync(appDir, { recursive: true });
249
+ fs.copyFileSync(srcFile, path.join(appDir, 'root.tsx'));
250
+ vitest_1.vi.spyOn(process, 'cwd').mockReturnValue(tmpDir);
251
+ await (0, root_1.instrumentRoot)('root.tsx');
252
+ const modifiedContent = fs.readFileSync(path.join(appDir, 'root.tsx'), 'utf8');
253
+ (0, vitest_1.expect)(modifiedContent).toContain('import { captureRemixErrorBoundaryError, withSentry } from "@sentry/remix";');
254
+ (0, vitest_1.expect)(modifiedContent).toContain('withSentry(App)');
255
+ });
256
+ });
31
257
  //# sourceMappingURL=root.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"root.test.js","sourceRoot":"","sources":["../../../test/remix/root.test.ts"],"names":[],"mappings":";;AAAA,kFAAkF;AAClF,uCAAuC;AACvC,mCAA8C;AAC9C,wDAAkE;AAElE,IAAA,iBAAQ,EAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,IAAA,WAAE,EAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,kCAAkC;QAClC,MAAM,eAAe,GAAG,IAAA,sBAAW,EAAC;;;;;;KAMnC,CAAC,CAAC;QAEH,IAAA,wBAAiB,EAAC,eAAe,EAAE,UAAU,CAAC,CAAC;QAE/C,MAAM,MAAM,GAAG,eAAe,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC;QAE/C,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,qBAAqB,CAAC;;;;;;;;;KASpC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["// @ts-expect-error - magicast is ESM and TS complains about that. It works though\nimport { parseModule } from 'magicast';\nimport { describe, expect, it } from 'vitest';\nimport { wrapAppWithSentry } from '../../src/remix/codemods/root';\n\ndescribe('wrapAppWithSentry', () => {\n it('should wrap the app with Sentry', () => {\n // Empty root.tsx file for testing\n const originalRootAst = parseModule(`\n import { Outlet } from '@remix-run/react';\n\n export default function App() {\n return <Outlet />;\n }\n `);\n\n wrapAppWithSentry(originalRootAst, 'root.tsx');\n\n const result = originalRootAst.generate().code;\n\n expect(result).toMatchInlineSnapshot(`\n \"import {withSentry} from '@sentry/remix';\n import { Outlet } from '@remix-run/react';\n\n function App() {\n return <Outlet />;\n }\n\n export default withSentry(App);\"\n `);\n });\n});\n"]}
1
+ {"version":3,"file":"root.test.js","sourceRoot":"","sources":["../../../test/remix/root.test.ts"],"names":[],"mappings":"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","sourcesContent":["// @ts-expect-error - magicast is ESM and TS complains about that. It works though\nimport { parseModule } from 'magicast';\nimport { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport {\n wrapAppWithSentry,\n isWithSentryAlreadyUsed,\n instrumentRoot,\n} from '../../src/remix/codemods/root';\n\nvi.mock('@clack/prompts', () => {\n const mock = {\n log: {\n warn: vi.fn(),\n info: vi.fn(),\n success: vi.fn(),\n },\n };\n return {\n default: mock,\n ...mock,\n };\n});\n\ndescribe('wrapAppWithSentry', () => {\n it('should wrap the app with Sentry', () => {\n // Empty root.tsx file for testing\n const originalRootAst = parseModule(`\n import { Outlet } from '@remix-run/react';\n\n export default function App() {\n return <Outlet />;\n }\n `);\n\n wrapAppWithSentry(originalRootAst, 'root.tsx');\n\n const result = originalRootAst.generate().code;\n\n expect(result).toMatchInlineSnapshot(`\n \"import {withSentry} from '@sentry/remix';\n import { Outlet } from '@remix-run/react';\n\n function App() {\n return <Outlet />;\n }\n\n export default withSentry(App);\"\n `);\n });\n});\n\ndescribe('isWithSentryAlreadyUsed', () => {\n it('should return false when withSentry is not used', () => {\n const rootAst = parseModule(`\n import { Outlet } from '@remix-run/react';\n\n export default function App() {\n return <Outlet />;\n }\n `);\n\n expect(isWithSentryAlreadyUsed(rootAst)).toBe(false);\n });\n\n it('should return true when withSentry is used in default export', () => {\n const rootAst = parseModule(`\n import { withSentry } from '@sentry/remix';\n import { Outlet } from '@remix-run/react';\n\n function App() {\n return <Outlet />;\n }\n\n export default withSentry(App);\n `);\n\n expect(isWithSentryAlreadyUsed(rootAst)).toBe(true);\n });\n\n it('should return true when withSentry is used in a variable assignment', () => {\n const rootAst = parseModule(`\n import { withSentry } from '@sentry/remix';\n import { Outlet } from '@remix-run/react';\n\n function App() {\n return <Outlet />;\n }\n\n const WrappedApp = withSentry(App);\n export default WrappedApp;\n `);\n\n expect(isWithSentryAlreadyUsed(rootAst)).toBe(true);\n });\n\n it('should return true when withSentry is used inside a function', () => {\n const rootAst = parseModule(`\n import { withSentry } from '@sentry/remix';\n import { Outlet } from '@remix-run/react';\n\n function App() {\n return <Outlet />;\n }\n\n function createApp() {\n return withSentry(App);\n }\n\n export default createApp();\n `);\n\n expect(isWithSentryAlreadyUsed(rootAst)).toBe(true);\n });\n\n it('should return false when withSentry is imported but not used', () => {\n const rootAst = parseModule(`\n import { withSentry } from '@sentry/remix';\n import { Outlet } from '@remix-run/react';\n\n export default function App() {\n return <Outlet />;\n }\n `);\n\n expect(isWithSentryAlreadyUsed(rootAst)).toBe(false);\n });\n\n it('should return false when a different function with similar name is used', () => {\n const rootAst = parseModule(`\n import { Outlet } from '@remix-run/react';\n\n function withSentryLike() {\n return null;\n }\n\n export default function App() {\n withSentryLike();\n return <Outlet />;\n }\n `);\n\n expect(isWithSentryAlreadyUsed(rootAst)).toBe(false);\n });\n});\n\ndescribe('instrumentRoot', () => {\n const fixturesDir = path.join(__dirname, 'fixtures');\n const tmpDir = path.join(fixturesDir, 'tmp');\n\n beforeEach(() => {\n vi.clearAllMocks();\n // Mock process.cwd() to return the fixtures directory\n vi.spyOn(process, 'cwd').mockReturnValue(fixturesDir);\n });\n\n afterEach(() => {\n // Clean up any temporary files\n if (fs.existsSync(tmpDir)) {\n try {\n // Remove files first, then directory\n const appDir = path.join(tmpDir, 'app');\n if (fs.existsSync(appDir)) {\n const files = fs.readdirSync(appDir);\n files.forEach((file) => {\n fs.unlinkSync(path.join(appDir, file));\n });\n fs.rmdirSync(appDir);\n }\n\n const files = fs.readdirSync(tmpDir);\n files.forEach((file) => {\n const filePath = path.join(tmpDir, file);\n const stat = fs.statSync(filePath);\n if (stat.isDirectory()) {\n fs.rmdirSync(filePath);\n } else {\n fs.unlinkSync(filePath);\n }\n });\n } catch (error) {\n // Ignore cleanup errors in tests\n }\n }\n });\n\n it('should add ErrorBoundary and wrap app with Sentry when no ErrorBoundary exists and withSentry is not used', async () => {\n // Copy fixture to tmp directory for testing\n const srcFile = path.join(fixturesDir, 'root-no-error-boundary.tsx');\n const appDir = path.join(tmpDir, 'app');\n\n // Create app directory and copy file\n fs.mkdirSync(appDir, { recursive: true });\n fs.copyFileSync(srcFile, path.join(appDir, 'root.tsx'));\n\n // Mock process.cwd() to return tmpDir\n vi.spyOn(process, 'cwd').mockReturnValue(tmpDir);\n\n await instrumentRoot('root.tsx');\n\n // Check that the file was modified correctly\n const modifiedContent = fs.readFileSync(\n path.join(appDir, 'root.tsx'),\n 'utf8',\n );\n\n expect(modifiedContent).toContain(\n 'import { captureRemixErrorBoundaryError, withSentry } from \"@sentry/remix\";',\n );\n expect(modifiedContent).toContain(\n \"import { Outlet, useRouteError } from '@remix-run/react';\",\n );\n expect(modifiedContent).toContain('withSentry(App)');\n expect(modifiedContent).toContain('const ErrorBoundary = () => {');\n });\n\n it('should wrap app with Sentry when ErrorBoundary exists but no Sentry content', async () => {\n const srcFile = path.join(fixturesDir, 'root-with-error-boundary.tsx');\n const appDir = path.join(tmpDir, 'app');\n\n fs.mkdirSync(appDir, { recursive: true });\n fs.copyFileSync(srcFile, path.join(appDir, 'root.tsx'));\n\n vi.spyOn(process, 'cwd').mockReturnValue(tmpDir);\n\n await instrumentRoot('root.tsx');\n\n const modifiedContent = fs.readFileSync(\n path.join(appDir, 'root.tsx'),\n 'utf8',\n );\n\n expect(modifiedContent).toContain(\n 'import { captureRemixErrorBoundaryError, withSentry } from \"@sentry/remix\";',\n );\n expect(modifiedContent).toContain('withSentry(App)');\n });\n\n it('should wrap app with Sentry when ErrorBoundary exists with Sentry content but withSentry is not used', async () => {\n const srcFile = path.join(\n fixturesDir,\n 'root-with-sentry-error-boundary.tsx',\n );\n const appDir = path.join(tmpDir, 'app');\n\n fs.mkdirSync(appDir, { recursive: true });\n fs.copyFileSync(srcFile, path.join(appDir, 'root.tsx'));\n\n vi.spyOn(process, 'cwd').mockReturnValue(tmpDir);\n\n await instrumentRoot('root.tsx');\n\n const modifiedContent = fs.readFileSync(\n path.join(appDir, 'root.tsx'),\n 'utf8',\n );\n\n expect(modifiedContent).toContain(\n \"import { captureRemixErrorBoundaryError, withSentry } from '@sentry/remix';\",\n );\n expect(modifiedContent).toContain('withSentry(App)');\n });\n\n it('should not wrap app when withSentry is already used', async () => {\n const srcFile = path.join(fixturesDir, 'root-already-wrapped.tsx');\n const appDir = path.join(tmpDir, 'app');\n\n fs.mkdirSync(appDir, { recursive: true });\n fs.copyFileSync(srcFile, path.join(appDir, 'root.tsx'));\n\n vi.spyOn(process, 'cwd').mockReturnValue(tmpDir);\n\n await instrumentRoot('root.tsx');\n\n const modifiedContent = fs.readFileSync(\n path.join(appDir, 'root.tsx'),\n 'utf8',\n );\n\n // Should not add withSentry import or wrap again\n expect(modifiedContent).toContain('withSentry(App)');\n\n // Count occurrences of withSentry to ensure it's not duplicated\n const withSentryOccurrences = (modifiedContent.match(/withSentry/g) || [])\n .length;\n expect(withSentryOccurrences).toBe(2); // One import, one usage\n\n // The content should remain largely the same since withSentry is already used\n expect(modifiedContent).toContain('export default withSentry(App)');\n });\n\n it('should handle ErrorBoundary as variable declaration', async () => {\n const srcFile = path.join(fixturesDir, 'root-variable-error-boundary.tsx');\n const appDir = path.join(tmpDir, 'app');\n\n fs.mkdirSync(appDir, { recursive: true });\n fs.copyFileSync(srcFile, path.join(appDir, 'root.tsx'));\n\n vi.spyOn(process, 'cwd').mockReturnValue(tmpDir);\n\n await instrumentRoot('root.tsx');\n\n const modifiedContent = fs.readFileSync(\n path.join(appDir, 'root.tsx'),\n 'utf8',\n );\n\n expect(modifiedContent).toContain(\n 'import { captureRemixErrorBoundaryError, withSentry } from \"@sentry/remix\";',\n );\n expect(modifiedContent).toContain('withSentry(App)');\n });\n});\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/wizard",
3
- "version": "6.1.2",
3
+ "version": "6.2.0",
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",
@@ -26,7 +26,6 @@
26
26
  "dist"
27
27
  ],
28
28
  "dependencies": {
29
- "@clack/core": "^0.3.4",
30
29
  "@clack/prompts": "0.7.0",
31
30
  "@sentry/node": "^7.119.2",
32
31
  "axios": "1.8.2",
@@ -44,29 +43,22 @@
44
43
  },
45
44
  "devDependencies": {
46
45
  "@babel/types": "~7.21.4",
47
- "@sentry-internal/eslint-config-sdk": "^7.48.0",
48
- "@types/chai": "^4.3.17",
49
46
  "@types/glob": "^7.2.0",
50
47
  "@types/inquirer": "^0.0.43",
51
- "@types/lodash": "^4.14.144",
52
48
  "@types/node": "^18.19.76",
53
49
  "@types/opn": "5.1.0",
54
- "@types/rimraf": "^3.0.2",
55
50
  "@types/semver": "^7.3.7",
56
51
  "@types/yargs": "^16.0.9",
57
52
  "@typescript-eslint/eslint-plugin": "^5.13.0",
58
53
  "@typescript-eslint/parser": "^5.13.0",
59
54
  "@vitest/coverage-v8": "3.0.9",
60
55
  "clifty": "0.2.7",
61
- "dotenv": "^16.4.5",
62
56
  "eslint": "^8.18.0",
63
57
  "eslint-config-prettier": "^8.3.0",
64
58
  "eslint-plugin-vitest": "^0.5.4",
65
59
  "fossilize": "^0.3.1",
66
60
  "prettier": "^2.8.7",
67
- "rimraf": "^3.0.2",
68
61
  "ts-node": "^10.9.1",
69
- "tsx": "^3.14.0",
70
62
  "typescript": "^5.0.4",
71
63
  "vite": "^6.2.3",
72
64
  "vitest": "^3.0.9"