@sentry/wizard
Version:
Sentry wizard helping you to configure your project
202 lines (198 loc) • 8.95 kB
JavaScript
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;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.addSentryExpoConfigRequire = exports.patchMetroInMemory = exports.addSentryToExpoMetroConfig = void 0;
const fs = __importStar(require("node:fs"));
// @ts-expect-error - clack is ESM and TS complains about that. It works though
const clack = __importStar(require("@clack/prompts"));
const chalk_1 = __importDefault(require("chalk"));
const Sentry = __importStar(require("@sentry/node"));
const ast_utils_1 = require("../utils/ast-utils");
const clack_1 = require("../utils/clack");
const metro_1 = require("./metro");
const recast = __importStar(require("recast"));
const b = recast.types.builders;
async function addSentryToExpoMetroConfig() {
let metroConfigPath = (0, metro_1.findMetroConfigPath)();
if (!metroConfigPath) {
// No existing metro config found, create metro.config.js (Expo default)
metroConfigPath = 'metro.config.js';
}
if (!fs.existsSync(metroConfigPath)) {
const success = await createSentryExpoMetroConfig(metroConfigPath);
if (!success) {
Sentry.setTag('expo-metro-config', 'create-new-error');
return await showInstructions(metroConfigPath);
}
Sentry.setTag('expo-metro-config', 'created-new');
return undefined;
}
Sentry.setTag('expo-metro-config', 'exists');
clack.log.info(`Updating existing ${metroConfigPath}.`);
const mod = await (0, metro_1.parseMetroConfig)(metroConfigPath);
if (!mod) {
return await showInstructions(metroConfigPath);
}
let didPatch = false;
try {
didPatch = patchMetroInMemory(mod, metroConfigPath);
}
catch (e) {
Sentry.captureException('Unable to patch expo metro config');
}
if (!didPatch) {
Sentry.setTag('expo-metro-config', 'patch-error');
clack.log.error(`Could not patch ${chalk_1.default.cyan(metroConfigPath)} with Sentry configuration.`);
return await showInstructions(metroConfigPath);
}
const saved = await (0, metro_1.writeMetroConfig)(mod, metroConfigPath);
if (saved) {
Sentry.setTag('expo-metro-config', 'patch-saved');
clack.log.success(chalk_1.default.green(`${chalk_1.default.cyan(metroConfigPath)} changes saved.`));
}
else {
Sentry.setTag('expo-metro-config', 'patch-save-error');
clack.log.error(`Could not save changes to ${chalk_1.default.cyan(metroConfigPath)}, please follow the manual steps.`);
return await showInstructions(metroConfigPath);
}
}
exports.addSentryToExpoMetroConfig = addSentryToExpoMetroConfig;
function patchMetroInMemory(mod, metroConfigPath) {
const ast = mod.$ast;
if ((0, ast_utils_1.hasSentryContent)(ast)) {
clack.log.warn(`The ${chalk_1.default.cyan(metroConfigPath)} file already has Sentry configuration.`);
return false;
}
let didReplaceDefaultConfigCall = false;
recast.visit(ast, {
visitVariableDeclaration(path) {
const { node } = path;
if (
// path is require("expo/metro-config")
// and only getDefaultConfig is being destructured
// then remove the entire declaration
node.declarations.length > 0 &&
node.declarations[0].type === 'VariableDeclarator' &&
node.declarations[0].init &&
node.declarations[0].init.type === 'CallExpression' &&
node.declarations[0].init.callee &&
node.declarations[0].init.callee.type === 'Identifier' &&
node.declarations[0].init.callee.name === 'require' &&
node.declarations[0].init.arguments[0].type === 'StringLiteral' &&
node.declarations[0].init.arguments[0].value === 'expo/metro-config' &&
node.declarations[0].id.type === 'ObjectPattern' &&
node.declarations[0].id.properties.length === 1 &&
node.declarations[0].id.properties[0].type === 'ObjectProperty' &&
node.declarations[0].id.properties[0].key.type === 'Identifier' &&
node.declarations[0].id.properties[0].key.name === 'getDefaultConfig') {
path.prune();
return false;
}
this.traverse(path);
},
visitCallExpression(path) {
const { node } = path;
if (
// path is getDefaultConfig
// then rename it to getSentryExpoConfig
node.callee.type === 'Identifier' &&
node.callee.name === 'getDefaultConfig') {
node.callee.name = 'getSentryExpoConfig';
didReplaceDefaultConfigCall = true;
return false;
}
this.traverse(path);
},
});
if (!didReplaceDefaultConfigCall) {
clack.log.warn(`Could not find \`getDefaultConfig\` in ${chalk_1.default.cyan(metroConfigPath)}.`);
return false;
}
addSentryExpoConfigRequire(ast, metroConfigPath);
return true;
}
exports.patchMetroInMemory = patchMetroInMemory;
function addSentryExpoConfigRequire(program, metroConfigPath) {
try {
const lastRequireIndex = (0, ast_utils_1.getLastRequireIndex)(program);
const sentryExpoConfigRequire = createSentryExpoConfigRequire();
// Add the require statement after the last require or at the beginning
program.body.splice(lastRequireIndex + 1, 0, sentryExpoConfigRequire);
}
catch (error) {
clack.log.error(`Could not add Sentry Expo config require statement to ${chalk_1.default.cyan(metroConfigPath)}.`);
Sentry.captureException(`Could not add Sentry Expo config require statement to ${metroConfigPath}.`);
}
}
exports.addSentryExpoConfigRequire = addSentryExpoConfigRequire;
/**
* Creates const { getSentryExpoConfig } = require("@sentry/react-native/metro");
*/
function createSentryExpoConfigRequire() {
return b.variableDeclaration('const', [
b.variableDeclarator(b.objectPattern([
b.objectProperty.from({
key: b.identifier('getSentryExpoConfig'),
value: b.identifier('getSentryExpoConfig'),
shorthand: true,
}),
]), b.callExpression(b.identifier('require'), [
b.literal('@sentry/react-native/metro'),
])),
]);
}
async function createSentryExpoMetroConfig(metroConfigPath) {
const snippet = `const { getSentryExpoConfig } = require("@sentry/react-native/metro");
const config = getSentryExpoConfig(__dirname);
module.exports = config;
`;
try {
await fs.promises.writeFile(metroConfigPath, snippet);
}
catch (e) {
clack.log.error(`Could not create ${chalk_1.default.cyan(metroConfigPath)} with Sentry configuration.`);
Sentry.captureException(`Could not create ${metroConfigPath} with Sentry configuration.`);
return false;
}
clack.log.success(`Created ${chalk_1.default.cyan(metroConfigPath)} with Sentry configuration.`);
return true;
}
function showInstructions(metroConfigPath) {
return (0, clack_1.showCopyPasteInstructions)({
filename: metroConfigPath,
codeSnippet: getMetroWithSentryExpoConfigSnippet(true),
});
}
function getMetroWithSentryExpoConfigSnippet(colors) {
return (0, clack_1.makeCodeSnippet)(colors, (unchanged, plus, minus) => unchanged(`${minus(`// const { getDefaultConfig } = require("expo/metro-config");`)}
${plus(`const { getSentryExpoConfig } = require("@sentry/react-native/metro");`)}
${minus(`// const config = getDefaultConfig(__dirname);`)}
${plus(`const config = getSentryExpoConfig(__dirname);`)}
module.exports = config;`));
}
//# sourceMappingURL=expo-metro.js.map
;