UNPKG

@sentry/wizard

Version:

Sentry wizard helping you to configure your project

399 lines (373 loc) 15.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); // @ts-expect-error - magicast is ESM and TS complains about that. It works though const magicast_1 = require("magicast"); const metro_1 = require("../../src/react-native/metro"); const vitest_1 = require("vitest"); const fs = __importStar(require("fs")); vitest_1.vi.mock('../../src/utils/clack/mcp-config', () => ({ offerProjectScopedMcpConfig: vitest_1.vi.fn().mockResolvedValue(undefined), })); vitest_1.vi.mock('fs', async () => { const actual = await vitest_1.vi.importActual('fs'); return { ...actual, existsSync: vitest_1.vi.fn(), }; }); (0, vitest_1.describe)('patch metro config - sentry serializer', () => { (0, vitest_1.describe)('patchMetroWithSentryConfigInMemory', () => { (0, vitest_1.it)('patches react native 0.72 default metro config', async () => { const mod = (0, magicast_1.parseModule)(`const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config'); /** * Metro configuration * https://reactnative.dev/docs/metro * * @type {import('metro-config').MetroConfig} */ const config = {}; module.exports = mergeConfig(getDefaultConfig(__dirname), config);`); const result = await (0, metro_1.patchMetroWithSentryConfigInMemory)(mod, 'metro.config.js', true); (0, vitest_1.expect)(result).toBe(true); (0, vitest_1.expect)((0, magicast_1.generateCode)(mod.$ast).code) .toBe(`const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config'); const { withSentryConfig } = require("@sentry/react-native/metro"); /** * Metro configuration * https://reactnative.dev/docs/metro * * @type {import('metro-config').MetroConfig} */ const config = {}; module.exports = withSentryConfig(mergeConfig(getDefaultConfig(__dirname), config));`); }); (0, vitest_1.it)('patches react native 0.65 default metro config', async () => { const mod = (0, magicast_1.parseModule)(`/** * Metro configuration for React Native * https://github.com/facebook/react-native * * @format */ module.exports = { transformer: { getTransformOptions: async () => ({ transform: { experimentalImportSupport: false, inlineRequires: true, }, }), }, };`); const result = await (0, metro_1.patchMetroWithSentryConfigInMemory)(mod, 'metro.config.js', true); (0, vitest_1.expect)(result).toBe(true); (0, vitest_1.expect)((0, magicast_1.generateCode)(mod.$ast).code).toBe(`const { withSentryConfig } = require("@sentry/react-native/metro"); /** * Metro configuration for React Native * https://github.com/facebook/react-native * * @format */ module.exports = withSentryConfig({ transformer: { getTransformOptions: async () => ({ transform: { experimentalImportSupport: false, inlineRequires: true, }, }), }, });`); }); (0, vitest_1.it)('patches react native metro config exported variable', async () => { const mod = (0, magicast_1.parseModule)(`const testConfig = {}; module.exports = testConfig;`); const result = await (0, metro_1.patchMetroWithSentryConfigInMemory)(mod, 'metro.config.js', true); (0, vitest_1.expect)(result).toBe(true); (0, vitest_1.expect)((0, magicast_1.generateCode)(mod.$ast).code).toBe(`const { withSentryConfig } = require("@sentry/react-native/metro"); const testConfig = {}; module.exports = withSentryConfig(testConfig);`); }); (0, vitest_1.it)('patches custom react native metro config', async () => { const mod = (0, magicast_1.parseModule)(`const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config'); const defaultConfig = getDefaultConfig(__dirname); const {assetExts, sourceExts} = defaultConfig.resolver; /** * Metro configuration * https://facebook.github.io/metro/docs/configuration * * @type {import('metro-config').MetroConfig} */ const jsoMetroPlugin = require('obfuscator-io-metro-plugin')( { // for these option look javascript-obfuscator library options from above url compact: false, sourceMap: false, controlFlowFlattening: true, controlFlowFlatteningThreshold: 1, numbersToExpressions: true, simplify: true, stringArrayShuffle: true, splitStrings: true, stringArrayThreshold: 1, }, { runInDev: false /* optional */, logObfuscatedFiles: true /* optional generated files will be located at ./.jso */, // source Map generated after obfuscation is not useful right now sourceMapLocation: './index.android.bundle.map' /* optional only works if sourceMap: true in obfuscation option */, }, ); const config = { transformer: { babelTransformerPath: require.resolve('react-native-svg-transformer'), getTransformOptions: async () => ({ transform: { experimentalImportSupport: false, inlineRequires: true, }, }), }, resolver: { assetExts: assetExts.filter(ext => ext !== 'svg'), sourceExts: [...sourceExts, 'svg'], }, ...jsoMetroPlugin, }; module.exports = mergeConfig(getDefaultConfig(__dirname), config);`); const result = await (0, metro_1.patchMetroWithSentryConfigInMemory)(mod, 'metro.config.js', true); (0, vitest_1.expect)(result).toBe(true); (0, vitest_1.expect)((0, magicast_1.generateCode)(mod.$ast).code) .toBe(`const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config'); const { withSentryConfig } = require("@sentry/react-native/metro"); const defaultConfig = getDefaultConfig(__dirname); const {assetExts, sourceExts} = defaultConfig.resolver; /** * Metro configuration * https://facebook.github.io/metro/docs/configuration * * @type {import('metro-config').MetroConfig} */ const jsoMetroPlugin = require('obfuscator-io-metro-plugin')( { // for these option look javascript-obfuscator library options from above url compact: false, sourceMap: false, controlFlowFlattening: true, controlFlowFlatteningThreshold: 1, numbersToExpressions: true, simplify: true, stringArrayShuffle: true, splitStrings: true, stringArrayThreshold: 1, }, { runInDev: false /* optional */, logObfuscatedFiles: true /* optional generated files will be located at ./.jso */, // source Map generated after obfuscation is not useful right now sourceMapLocation: './index.android.bundle.map' /* optional only works if sourceMap: true in obfuscation option */, }, ); const config = { transformer: { babelTransformerPath: require.resolve('react-native-svg-transformer'), getTransformOptions: async () => ({ transform: { experimentalImportSupport: false, inlineRequires: true, }, }), }, resolver: { assetExts: assetExts.filter(ext => ext !== 'svg'), sourceExts: [...sourceExts, 'svg'], }, ...jsoMetroPlugin, }; module.exports = withSentryConfig(mergeConfig(getDefaultConfig(__dirname), config));`); }); (0, vitest_1.it)('patches CJS style metro config', async () => { const mod = (0, magicast_1.parseModule)(`const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config'); const config = {}; module.exports = mergeConfig(getDefaultConfig(__dirname), config);`); const result = await (0, metro_1.patchMetroWithSentryConfigInMemory)(mod, 'metro.config.cjs', true); (0, vitest_1.expect)(result).toBe(true); const code = (0, magicast_1.generateCode)(mod.$ast).code; (0, vitest_1.expect)(code).toContain('require("@sentry/react-native/metro")'); (0, vitest_1.expect)(code).toContain('withSentryConfig'); }); (0, vitest_1.it)('does not patch react native metro config exported as factory function', async () => { const mod = (0, magicast_1.parseModule)(`module.exports = () => ({});`); const result = await (0, metro_1.patchMetroWithSentryConfigInMemory)(mod, 'metro.config.js', true); (0, vitest_1.expect)(result).toBe(false); (0, vitest_1.expect)((0, magicast_1.generateCode)(mod.$ast).code).toBe(`module.exports = () => ({});`); }); }); (0, vitest_1.describe)('addSentrySerializerToMetroConfig', () => { (0, vitest_1.it)('add to empty config', () => { const mod = (0, magicast_1.parseModule)(`module.exports = { other: 'config' }`); const configObject = getModuleExportsObject(mod); const result = (0, metro_1.addSentrySerializerToMetroConfig)(configObject); (0, vitest_1.expect)(result).toBe(true); (0, vitest_1.expect)((0, magicast_1.generateCode)(mod.$ast).code).toBe(`module.exports = { other: 'config', serializer: { customSerializer: createSentryMetroSerializer() } }`); }); (0, vitest_1.it)('add to existing serializer config', () => { const mod = (0, magicast_1.parseModule)(`module.exports = { other: 'config', serializer: { other: 'config' } }`); const configObject = getModuleExportsObject(mod); const result = (0, metro_1.addSentrySerializerToMetroConfig)(configObject); (0, vitest_1.expect)(result).toBe(true); (0, vitest_1.expect)((0, magicast_1.generateCode)(mod.$ast).code).toBe(`module.exports = { other: 'config', serializer: { other: 'config', customSerializer: createSentryMetroSerializer() } }`); }); (0, vitest_1.it)('not add to existing customSerializer config', () => { const mod = (0, magicast_1.parseModule)(`module.exports = { other: 'config', serializer: { other: 'config', customSerializer: 'existing-serializer' } }`); const configObject = getModuleExportsObject(mod); const result = (0, metro_1.addSentrySerializerToMetroConfig)(configObject); (0, vitest_1.expect)(result).toBe(false); (0, vitest_1.expect)((0, magicast_1.generateCode)(mod.$ast).code).toBe(`module.exports = { other: 'config', serializer: { other: 'config', customSerializer: 'existing-serializer' } }`); }); }); (0, vitest_1.describe)('addSentrySerializerImportToMetroConfig', () => { (0, vitest_1.it)('add import', () => { const mod = (0, magicast_1.parseModule)(`const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config'); module.exports = { other: 'config' }`); const result = (0, metro_1.addSentrySerializerRequireToMetroConfig)(mod.$ast); (0, vitest_1.expect)(result).toBe(true); (0, vitest_1.expect)((0, magicast_1.generateCode)(mod.$ast).code) .toBe(`const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config'); const { createSentryMetroSerializer } = require("@sentry/react-native/dist/js/tools/sentryMetroSerializer"); module.exports = { other: 'config' }`); }); }); (0, vitest_1.describe)('getMetroConfigObject', () => { (0, vitest_1.it)('get config object from variable called config', () => { const mod = (0, magicast_1.parseModule)(`var config = { some: 'config' };`); const configObject = (0, metro_1.getMetroConfigObject)(mod.$ast); (0, vitest_1.expect)((configObject?.properties[0]).key .name).toBe('some'); (0, vitest_1.expect)((configObject?.properties[0]) .value.value).toBe('config'); }); (0, vitest_1.it)('get config object from const called config', () => { const mod = (0, magicast_1.parseModule)(`const config = { some: 'config' };`); const configObject = (0, metro_1.getMetroConfigObject)(mod.$ast); (0, vitest_1.expect)((configObject?.properties[0]).key .name).toBe('some'); (0, vitest_1.expect)((configObject?.properties[0]) .value.value).toBe('config'); }); (0, vitest_1.it)('get config oject from let called config', () => { const mod = (0, magicast_1.parseModule)(`let config = { some: 'config' };`); const configObject = (0, metro_1.getMetroConfigObject)(mod.$ast); (0, vitest_1.expect)((configObject?.properties[0]).key .name).toBe('some'); (0, vitest_1.expect)((configObject?.properties[0]) .value.value).toBe('config'); }); (0, vitest_1.it)('get config object from module.exports', () => { const mod = (0, magicast_1.parseModule)(`module.exports = { some: 'config' };`); const configObject = (0, metro_1.getMetroConfigObject)(mod.$ast); (0, vitest_1.expect)((configObject?.properties[0]).key .name).toBe('some'); (0, vitest_1.expect)((configObject?.properties[0]) .value.value).toBe('config'); }); }); }); (0, vitest_1.describe)('Dynamic Metro Config path', () => { (0, vitest_1.beforeEach)(() => { vitest_1.vi.clearAllMocks(); }); (0, vitest_1.it)('finds metro.config.js when it exists', () => { vitest_1.vi.mocked(fs.existsSync).mockImplementation((path) => path === 'metro.config.js'); const result = (0, metro_1.findMetroConfigPath)(); (0, vitest_1.expect)(result).toBe('metro.config.js'); }); (0, vitest_1.it)('finds metro.config.cjs when it exists', () => { vitest_1.vi.mocked(fs.existsSync).mockImplementation((path) => path === 'metro.config.cjs'); const result = (0, metro_1.findMetroConfigPath)(); (0, vitest_1.expect)(result).toBe('metro.config.cjs'); }); (0, vitest_1.it)('prefers metro.config.js over metro.config.cjs when both exist', () => { vitest_1.vi.mocked(fs.existsSync).mockImplementation(() => true); const result = (0, metro_1.findMetroConfigPath)(); (0, vitest_1.expect)(result).toBe('metro.config.js'); }); (0, vitest_1.it)('returns undefined when no metro config exists', () => { vitest_1.vi.mocked(fs.existsSync).mockImplementation(() => false); const result = (0, metro_1.findMetroConfigPath)(); (0, vitest_1.expect)(result).toBeUndefined(); }); }); function getModuleExportsObject(mod, index = 0) { return mod.$ast.body[index] .expression.right; } //# sourceMappingURL=metro.test.js.map