UNPKG

@sentry/wizard

Version:

Sentry wizard helping you to configure your project

350 lines (347 loc) 17.8 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; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.runReactNativeWizardWithTelemetry = exports.runReactNativeWizard = exports.METRO_WITH_SENTRY_CONFIG_SUPPORTED_SDK_RANGE = exports.EXPO_SUPPORTED_SDK_RANGE = exports.SENTRY_METRO_PLUGIN_SUPPORTED_SDK_RANGE = exports.XCODE_SCRIPTS_SUPPORTED_SDK_RANGE = exports.SUPPORTED_EXPO_RANGE = exports.SUPPORTED_RN_RANGE = exports.RN_HUMAN_NAME = exports.RN_PACKAGE = exports.RN_SDK_SUPPORTED_RANGE = exports.RN_SDK_PACKAGE = void 0; /* eslint-disable max-lines */ // @ts-ignore - clack is ESM and TS complains about that. It works though const prompts_1 = __importDefault(require("@clack/prompts")); const chalk_1 = __importDefault(require("chalk")); const fs = __importStar(require("fs")); const clack_utils_1 = require("../utils/clack-utils"); const package_json_1 = require("../utils/package-json"); const cocoapod_1 = require("../apple/cocoapod"); const os_1 = require("os"); const xcode_1 = require("./xcode"); const gradle_1 = require("./gradle"); const uninstall_1 = require("./uninstall"); const glob_1 = require("./glob"); const javascript_1 = require("./javascript"); const telemetry_1 = require("../telemetry"); const Sentry = __importStar(require("@sentry/node")); const semver_1 = require("../utils/semver"); const url_1 = require("../utils/url"); const metro_1 = require("./metro"); const expo_1 = require("./expo"); const expo_metro_1 = require("./expo-metro"); const expo_env_file_1 = require("./expo-env-file"); const xcode_2 = __importDefault(require("xcode")); exports.RN_SDK_PACKAGE = '@sentry/react-native'; exports.RN_SDK_SUPPORTED_RANGE = '>=5.0.0'; exports.RN_PACKAGE = 'react-native'; exports.RN_HUMAN_NAME = 'React Native'; exports.SUPPORTED_RN_RANGE = '>=0.69.0'; exports.SUPPORTED_EXPO_RANGE = '>=50.0.0'; /** * The following SDK version ship with bundled Xcode scripts * which simplifies the Xcode Build Phases setup. */ exports.XCODE_SCRIPTS_SUPPORTED_SDK_RANGE = '>=5.11.0'; /** * The following SDK version ship with Sentry Metro plugin */ exports.SENTRY_METRO_PLUGIN_SUPPORTED_SDK_RANGE = '>=5.11.0'; /** * The following SDK version ship with bundled Expo plugin */ exports.EXPO_SUPPORTED_SDK_RANGE = `>=5.16.0`; // The following SDK version shipped `withSentryConfig` exports.METRO_WITH_SENTRY_CONFIG_SUPPORTED_SDK_RANGE = '>=5.17.0'; async function runReactNativeWizard(params) { return (0, telemetry_1.withTelemetry)({ enabled: params.telemetryEnabled, integration: 'react-native', wizardOptions: params, }, () => runReactNativeWizardWithTelemetry(params)); } exports.runReactNativeWizard = runReactNativeWizard; async function runReactNativeWizardWithTelemetry(options) { if (options.uninstall) { Sentry.setTag('uninstall', true); return (0, uninstall_1.runReactNativeUninstall)(options); } const { promoCode, telemetryEnabled, forceInstall } = options; (0, clack_utils_1.printWelcome)({ wizardName: 'Sentry React Native Wizard', promoCode, telemetryEnabled, }); await (0, clack_utils_1.confirmContinueIfNoOrDirtyGitRepo)(); const packageJson = await (0, clack_utils_1.getPackageDotJson)(); const hasInstalled = (dep) => (0, package_json_1.hasPackageInstalled)(dep, packageJson); if (hasInstalled('sentry-expo')) { Sentry.setTag('has-sentry-expo-installed', true); (0, expo_1.printSentryExpoMigrationOutro)(); return; } await (0, clack_utils_1.ensurePackageIsInstalled)(packageJson, exports.RN_PACKAGE, exports.RN_HUMAN_NAME); const rnVersion = (0, package_json_1.getPackageVersion)(exports.RN_PACKAGE, packageJson); if (rnVersion) { await (0, clack_utils_1.confirmContinueIfPackageVersionNotSupported)({ packageName: exports.RN_HUMAN_NAME, packageVersion: rnVersion, packageId: exports.RN_PACKAGE, acceptableVersions: exports.SUPPORTED_RN_RANGE, note: `Please upgrade to ${exports.SUPPORTED_RN_RANGE} if you wish to use the Sentry Wizard. Or setup using ${chalk_1.default.cyan('https://docs.sentry.io/platforms/react-native/manual-setup/manual-setup/')}`, }); } await (0, clack_utils_1.installPackage)({ packageName: exports.RN_SDK_PACKAGE, alreadyInstalled: (0, package_json_1.hasPackageInstalled)(exports.RN_SDK_PACKAGE, packageJson), forceInstall, }); const sdkVersion = (0, package_json_1.getPackageVersion)(exports.RN_SDK_PACKAGE, await (0, clack_utils_1.getPackageDotJson)()); if (sdkVersion) { await (0, clack_utils_1.confirmContinueIfPackageVersionNotSupported)({ packageName: 'Sentry React Native SDK', packageVersion: sdkVersion, packageId: exports.RN_SDK_PACKAGE, acceptableVersions: exports.RN_SDK_SUPPORTED_RANGE, note: `Please upgrade to ${exports.RN_SDK_SUPPORTED_RANGE} to continue with the wizard in this project.`, }); } else { const continueWithoutSdk = await (0, clack_utils_1.abortIfCancelled)(prompts_1.default.confirm({ message: 'Could not detect Sentry React Native SDK version. Do you want to continue anyway?', })); if (!continueWithoutSdk) { await (0, clack_utils_1.abort)(undefined, 0); } } Sentry.setTag(`detected-sentry-react-native-sdk-version`, sdkVersion); const expoVersion = (0, package_json_1.getPackageVersion)('expo', packageJson); const isExpo = !!expoVersion; if (expoVersion && sdkVersion) { await (0, clack_utils_1.confirmContinueIfPackageVersionNotSupported)({ packageName: 'Sentry React Native SDK', packageVersion: sdkVersion, packageId: exports.RN_SDK_PACKAGE, acceptableVersions: exports.EXPO_SUPPORTED_SDK_RANGE, note: `Please upgrade to ${exports.EXPO_SUPPORTED_SDK_RANGE} to continue with the wizard in this Expo project.`, }); await (0, clack_utils_1.confirmContinueIfPackageVersionNotSupported)({ packageName: 'Expo SDK', packageVersion: expoVersion, packageId: 'expo', acceptableVersions: exports.SUPPORTED_EXPO_RANGE, note: `Please upgrade to ${exports.SUPPORTED_EXPO_RANGE} to continue with the wizard in this Expo project.`, }); } const { selectedProject, authToken, sentryUrl } = await (0, clack_utils_1.getOrAskForProjectData)(options, 'react-native'); const orgSlug = selectedProject.organization.slug; const projectSlug = selectedProject.slug; const projectId = selectedProject.id; const cliConfig = { authToken, org: orgSlug, project: projectSlug, url: sentryUrl, }; await (0, telemetry_1.traceStep)('patch-app-js', () => (0, javascript_1.addSentryInit)({ dsn: selectedProject.keys[0].dsn.public })); if (isExpo) { await (0, telemetry_1.traceStep)('patch-expo-app-config', () => (0, expo_1.patchExpoAppConfig)(cliConfig)); await (0, telemetry_1.traceStep)('add-expo-env-local', () => (0, expo_env_file_1.addExpoEnvLocal)(cliConfig)); } if (isExpo) { await (0, telemetry_1.traceStep)('patch-metro-config', expo_metro_1.addSentryToExpoMetroConfig); } else { await (0, telemetry_1.traceStep)('patch-metro-config', () => addSentryToMetroConfig({ sdkVersion })); } if (fs.existsSync('ios')) { Sentry.setTag('patch-ios', true); await (0, telemetry_1.traceStep)('patch-xcode-files', () => patchXcodeFiles(cliConfig, { sdkVersion })); } if (fs.existsSync('android')) { Sentry.setTag('patch-android', true); await (0, telemetry_1.traceStep)('patch-android-files', () => patchAndroidFiles(cliConfig)); } await (0, clack_utils_1.runPrettierIfInstalled)(); const confirmedFirstException = await confirmFirstSentryException(sentryUrl, orgSlug, projectId); Sentry.setTag('user-confirmed-first-error', confirmedFirstException); if (confirmedFirstException) { prompts_1.default.outro(`${chalk_1.default.green('Everything is set up!')} ${chalk_1.default.dim('If you encounter any issues, let us know here: https://github.com/getsentry/sentry-react-native/issues')}`); } else { prompts_1.default.outro(`${chalk_1.default.dim('Let us know here: https://github.com/getsentry/sentry-react-native/issues')}`); } } exports.runReactNativeWizardWithTelemetry = runReactNativeWizardWithTelemetry; function addSentryToMetroConfig({ sdkVersion, }) { if (sdkVersion && (0, semver_1.fulfillsVersionRange)({ version: sdkVersion, acceptableVersions: exports.METRO_WITH_SENTRY_CONFIG_SUPPORTED_SDK_RANGE, canBeLatest: true, })) { return (0, metro_1.patchMetroWithSentryConfig)(); } if (sdkVersion && (0, semver_1.fulfillsVersionRange)({ version: sdkVersion, acceptableVersions: exports.SENTRY_METRO_PLUGIN_SUPPORTED_SDK_RANGE, canBeLatest: true, })) { return (0, metro_1.patchMetroConfigWithSentrySerializer)(); } } async function confirmFirstSentryException(url, orgSlug, projectId) { const issuesStreamUrl = (0, url_1.getIssueStreamUrl)({ url, orgSlug, projectId }); prompts_1.default.log .step(`To make sure everything is set up correctly, put the following code snippet into your application. The snippet will create a button that, when tapped, sends a test event to Sentry. After that check your project issues: ${chalk_1.default.cyan(issuesStreamUrl)}`); // We want the code snippet to be easily copy-pasteable, without any clack artifacts // eslint-disable-next-line no-console console.log(chalk_1.default.greenBright(` <Button title='Try!' onPress={ () => { Sentry.captureException(new Error('First error')) }}/> `)); const firstErrorConfirmed = prompts_1.default.confirm({ message: `Have you successfully sent a test event?`, }); return firstErrorConfirmed; } async function patchXcodeFiles(config, context) { await (0, clack_utils_1.addSentryCliConfig)(config, { ...clack_utils_1.propertiesCliSetupConfig, name: 'source maps and iOS debug files', filename: 'ios/sentry.properties', gitignore: false, }); if ((0, os_1.platform)() === 'darwin' && (await confirmPodInstall())) { await (0, telemetry_1.traceStep)('pod-install', () => (0, cocoapod_1.podInstall)('ios')); } const xcodeProjectPath = (0, telemetry_1.traceStep)('find-xcode-project', () => (0, glob_1.getFirstMatchedPath)(glob_1.XCODE_PROJECT)); Sentry.setTag('xcode-project-status', xcodeProjectPath ? 'found' : 'not-found'); if (!xcodeProjectPath) { prompts_1.default.log.warn(`Could not find Xcode project file using ${chalk_1.default.cyan(glob_1.XCODE_PROJECT)}.`); return; } // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const [xcodeProject, buildPhasesMap] = (0, telemetry_1.traceStep)('parse-xcode-project', () => { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call const project = xcode_2.default.project(xcodeProjectPath); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call project.parseSync(); const map = (0, xcode_1.getValidExistingBuildPhases)(project); return [project, map]; }); Sentry.setTag('xcode-project-status', 'parsed'); (0, telemetry_1.traceStep)('patch-bundle-phase', () => { const bundlePhase = (0, xcode_1.findBundlePhase)(buildPhasesMap); Sentry.setTag('xcode-bundle-phase-status', bundlePhase ? 'found' : 'not-found'); if (context.sdkVersion && (0, semver_1.fulfillsVersionRange)({ version: context.sdkVersion, acceptableVersions: exports.XCODE_SCRIPTS_SUPPORTED_SDK_RANGE, canBeLatest: true, })) { (0, xcode_1.patchBundlePhase)(bundlePhase, xcode_1.addSentryWithBundledScriptsToBundleShellScript); } else { (0, xcode_1.patchBundlePhase)(bundlePhase, xcode_1.addSentryWithCliToBundleShellScript); } Sentry.setTag('xcode-bundle-phase-status', 'patched'); }); (0, telemetry_1.traceStep)('add-debug-files-upload-phase', () => { const debugFilesUploadPhaseExists = !!(0, xcode_1.findDebugFilesUploadPhase)(buildPhasesMap); Sentry.setTag('xcode-debug-files-upload-phase-status', debugFilesUploadPhaseExists ? 'already-exists' : undefined); if (context.sdkVersion && (0, semver_1.fulfillsVersionRange)({ version: context.sdkVersion, acceptableVersions: exports.XCODE_SCRIPTS_SUPPORTED_SDK_RANGE, canBeLatest: true, })) { (0, xcode_1.addDebugFilesUploadPhaseWithBundledScripts)(xcodeProject, { debugFilesUploadPhaseExists, }); } else { (0, xcode_1.addDebugFilesUploadPhaseWithCli)(xcodeProject, { debugFilesUploadPhaseExists, }); } Sentry.setTag('xcode-debug-files-upload-phase-status', 'added'); }); (0, telemetry_1.traceStep)('write-xcode-project', () => { (0, xcode_1.writeXcodeProject)(xcodeProjectPath, xcodeProject); }); Sentry.setTag('xcode-project-status', 'patched'); } async function patchAndroidFiles(config) { await (0, clack_utils_1.addSentryCliConfig)(config, { ...clack_utils_1.propertiesCliSetupConfig, name: 'source maps and iOS debug files', filename: 'android/sentry.properties', gitignore: false, }); const appBuildGradlePath = (0, telemetry_1.traceStep)('find-app-build-gradle', () => (0, glob_1.getFirstMatchedPath)(glob_1.APP_BUILD_GRADLE)); Sentry.setTag('app-build-gradle-status', appBuildGradlePath ? 'found' : 'not-found'); if (!appBuildGradlePath) { prompts_1.default.log.warn(`Could not find Android ${chalk_1.default.cyan('app/build.gradle')} file using ${chalk_1.default.cyan(glob_1.APP_BUILD_GRADLE)}.`); return; } const appBuildGradle = (0, telemetry_1.traceStep)('read-app-build-gradle', () => fs.readFileSync(appBuildGradlePath, 'utf-8')); const includesSentry = (0, gradle_1.doesAppBuildGradleIncludeRNSentryGradlePlugin)(appBuildGradle); if (includesSentry) { Sentry.setTag('app-build-gradle-status', 'already-includes-sentry'); prompts_1.default.log.warn(`Android ${chalk_1.default.cyan('app/build.gradle')} file already includes Sentry.`); return; } const patchedAppBuildGradle = (0, telemetry_1.traceStep)('add-rn-sentry-gradle-plugin', () => (0, gradle_1.addRNSentryGradlePlugin)(appBuildGradle)); if (!(0, gradle_1.doesAppBuildGradleIncludeRNSentryGradlePlugin)(patchedAppBuildGradle)) { Sentry.setTag('app-build-gradle-status', 'failed-to-add-rn-sentry-gradle-plugin'); prompts_1.default.log.warn(`Could not add Sentry RN Gradle Plugin to ${chalk_1.default.cyan('app/build.gradle')}.`); return; } Sentry.setTag('app-build-gradle-status', 'added-rn-sentry-gradle-plugin'); prompts_1.default.log.success(`Added Sentry RN Gradle Plugin to ${chalk_1.default.bold('app/build.gradle')}.`); (0, telemetry_1.traceStep)('write-app-build-gradle', () => (0, gradle_1.writeAppBuildGradle)(appBuildGradlePath, patchedAppBuildGradle)); prompts_1.default.log.success(chalk_1.default.green(`Android ${chalk_1.default.cyan('app/build.gradle')} saved.`)); } async function confirmPodInstall() { return (0, telemetry_1.traceStep)('confirm-pod-install', async () => { const continueWithPodInstall = await (0, clack_utils_1.abortIfCancelled)(prompts_1.default.select({ message: 'Do you want to run `pod install` now?', options: [ { value: true, label: 'Yes', hint: 'Recommended for smaller projects, this might take several minutes', }, { value: false, label: `No, I'll do it later` }, ], initialValue: true, })); Sentry.setTag('continue-with-pod-install', continueWithPodInstall); return continueWithPodInstall; }); } //# sourceMappingURL=react-native-wizard.js.map