@sentry/wizard
Version:
Sentry wizard helping you to configure your project
399 lines (373 loc) • 15.6 kB
JavaScript
"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