UNPKG

@sentry/wizard

Version:

Sentry wizard helping you to configure your project

255 lines (253 loc) 13.5 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 }); const path = __importStar(require("node:path")); const fs = __importStar(require("node:fs")); const Constants_1 = require("../../lib/Constants"); const utils_1 = require("../utils"); const vitest_1 = require("vitest"); async function runWizardOnReactRouterProject(projectDir, integration) { const wizardInstance = (0, utils_1.startWizardInstance)(integration, projectDir); const packageManagerPrompted = await wizardInstance.waitForOutput('Please select your package manager.'); const tracingOptionPrompted = packageManagerPrompted && (await wizardInstance.sendStdinAndWaitForOutput([utils_1.KEYS.DOWN, utils_1.KEYS.ENTER], 'to track the performance of your application?', { timeout: 240000 })); const replayOptionPrompted = tracingOptionPrompted && (await wizardInstance.sendStdinAndWaitForOutput([utils_1.KEYS.ENTER], 'to get a video-like reproduction of errors during a user session?')); const logOptionPrompted = replayOptionPrompted && (await wizardInstance.sendStdinAndWaitForOutput([utils_1.KEYS.ENTER], 'to send your application logs to Sentry?')); const profilingOptionPrompted = logOptionPrompted && (await wizardInstance.sendStdinAndWaitForOutput([utils_1.KEYS.ENTER], 'to track application performance in detail?')); const examplePagePrompted = profilingOptionPrompted && (await wizardInstance.sendStdinAndWaitForOutput([utils_1.KEYS.ENTER], 'Do you want to create an example page')); const mcpPrompted = examplePagePrompted && (await wizardInstance.sendStdinAndWaitForOutput([utils_1.KEYS.ENTER], 'Optionally add a project-scoped MCP server configuration for the Sentry MCP?', { optional: true })); mcpPrompted && (await wizardInstance.sendStdinAndWaitForOutput([utils_1.KEYS.DOWN, utils_1.KEYS.ENTER], 'Successfully installed the Sentry React Router SDK!')); wizardInstance.kill(); } function checkReactRouterProject(projectDir, integration) { (0, vitest_1.test)('package.json is updated correctly', () => { (0, utils_1.checkPackageJson)(projectDir, integration); }); (0, vitest_1.test)('.env.sentry-build-plugin is created and contains the auth token', () => { (0, utils_1.checkEnvBuildPlugin)(projectDir); }); (0, vitest_1.test)('example page exists', () => { (0, utils_1.checkFileExists)(`${projectDir}/app/routes/sentry-example-page.tsx`); }); (0, vitest_1.test)('example API route exists', () => { (0, utils_1.checkFileExists)(`${projectDir}/app/routes/api.sentry-example-api.ts`); }); (0, vitest_1.test)('example page is added to routes configuration', () => { (0, utils_1.checkFileContents)(`${projectDir}/app/routes.ts`, [ 'route("/sentry-example-page", "routes/sentry-example-page.tsx")', 'route("/api/sentry-example-api", "routes/api.sentry-example-api.ts")', ]); }); (0, vitest_1.test)('instrument.server file exists', () => { (0, utils_1.checkFileExists)(`${projectDir}/instrument.server.mjs`); }); (0, vitest_1.test)('entry.client file contains Sentry initialization', () => { (0, utils_1.checkFileContents)(`${projectDir}/app/entry.client.tsx`, [ 'import * as Sentry from', '@sentry/react-router', `Sentry.init({ dsn: "${utils_1.TEST_ARGS.PROJECT_DSN}",`, 'integrations: [Sentry.reactRouterTracingIntegration(), Sentry.replayIntegration()]', 'enableLogs: true,', 'tracesSampleRate: 1.0,', ]); }); (0, vitest_1.test)('package.json scripts are updated correctly', () => { (0, utils_1.checkFileContents)(`${projectDir}/package.json`, [ `"start": "NODE_OPTIONS='--import ./instrument.server.mjs' react-router-serve ./build/server/index.js"`, `"dev": "NODE_OPTIONS='--import ./instrument.server.mjs' react-router dev"`, ]); }); (0, vitest_1.test)('entry.server file contains Sentry instrumentation', () => { (0, utils_1.checkFileContents)(`${projectDir}/app/entry.server.tsx`, [ 'import * as Sentry from', '@sentry/react-router', 'export const handleError = Sentry.createSentryHandleError(', 'export default Sentry.wrapSentryHandleRequest(handleRequest);' ]); }); (0, vitest_1.test)('instrument.server file contains Sentry initialization', () => { (0, utils_1.checkFileContents)(`${projectDir}/instrument.server.mjs`, [ 'import * as Sentry from \'@sentry/react-router\';', `Sentry.init({ dsn: "${utils_1.TEST_ARGS.PROJECT_DSN}",`, 'enableLogs: true,', ]); }); (0, vitest_1.test)('root file contains Sentry ErrorBoundary', () => { (0, utils_1.checkFileContents)(`${projectDir}/app/root.tsx`, [ 'import * as Sentry from', '@sentry/react-router', 'export function ErrorBoundary', 'Sentry.captureException(error)', ]); }); (0, vitest_1.test)('vite.config file contains sentryReactRouter plugin', () => { (0, utils_1.checkFileContents)(`${projectDir}/vite.config.ts`, [ 'import { sentryReactRouter } from', '@sentry/react-router', 'sentryReactRouter(', 'authToken: process.env.SENTRY_AUTH_TOKEN', ]); }); (0, vitest_1.test)('react-router.config file contains buildEnd hook with sentryOnBuildEnd', () => { (0, utils_1.checkFileContents)(`${projectDir}/react-router.config.ts`, [ 'import { sentryOnBuildEnd } from', '@sentry/react-router', 'ssr: true,', 'buildEnd: async', 'await sentryOnBuildEnd({', ]); }); (0, vitest_1.test)('builds successfully', async () => { await (0, utils_1.checkIfBuilds)(projectDir); }, 60000); // 1 minute timeout (0, vitest_1.test)('runs on dev mode correctly', async () => { await (0, utils_1.checkIfRunsOnDevMode)(projectDir, 'to expose'); }, 30000); // 30 second timeout (0, vitest_1.test)('runs on prod mode correctly', async () => { await (0, utils_1.checkIfRunsOnProdMode)(projectDir, 'react-router-serve'); }, 30000); // 30 second timeout } (0, vitest_1.describe)('React Router', () => { (0, vitest_1.describe)('with empty project', () => { const integration = Constants_1.Integration.reactRouter; const projectDir = path.resolve(__dirname, '../test-applications/react-router-test-app'); (0, vitest_1.beforeAll)(async () => { await runWizardOnReactRouterProject(projectDir, integration); }); (0, vitest_1.afterAll)(() => { (0, utils_1.revertLocalChanges)(projectDir); (0, utils_1.cleanupGit)(projectDir); }); checkReactRouterProject(projectDir, integration); }); (0, vitest_1.describe)('edge cases', () => { const baseProjectDir = path.resolve(__dirname, '../test-applications/react-router-test-app'); (0, vitest_1.describe)('existing Sentry setup', () => { const integration = Constants_1.Integration.reactRouter; const projectDir = path.resolve(__dirname, '../test-applications/react-router-test-app-existing'); (0, vitest_1.beforeAll)(async () => { // Copy project and add existing Sentry setup fs.cpSync(baseProjectDir, projectDir, { recursive: true }); const clientEntryPath = path.join(projectDir, 'app', 'entry.client.tsx'); const existingContent = `import * as Sentry from "@sentry/react-router"; import { startTransition, StrictMode } from "react"; import { hydrateRoot } from "react-dom/client"; import { HydratedRouter } from "react-router/dom"; Sentry.init({ dsn: "https://existing@dsn.ingest.sentry.io/1337", tracesSampleRate: 1.0, }); startTransition(() => { hydrateRoot( document, <StrictMode> <HydratedRouter /> </StrictMode> ); });`; fs.writeFileSync(clientEntryPath, existingContent); await runWizardOnReactRouterProject(projectDir, integration); }); (0, vitest_1.afterAll)(() => { (0, utils_1.revertLocalChanges)(projectDir); (0, utils_1.cleanupGit)(projectDir); try { fs.rmSync(projectDir, { recursive: true, force: true }); } catch (e) { // Ignore cleanup errors } }); (0, vitest_1.test)('wizard handles existing Sentry without duplication', () => { const clientContent = fs.readFileSync(`${projectDir}/app/entry.client.tsx`, 'utf8'); const sentryImportCount = (clientContent.match(/import \* as Sentry from "@sentry\/react-router"/g) || []).length; const sentryInitCount = (clientContent.match(/Sentry\.init\(/g) || []).length; (0, vitest_1.expect)(sentryImportCount).toBe(1); (0, vitest_1.expect)(sentryInitCount).toBe(1); }); // Only test the essential checks for this edge case (0, vitest_1.test)('package.json is updated correctly', () => { (0, utils_1.checkPackageJson)(projectDir, integration); }); (0, vitest_1.test)('essential files exist or wizard completes gracefully', () => { // Check if key directories exist (0, vitest_1.expect)(fs.existsSync(`${projectDir}/app`)).toBe(true); // When there's existing Sentry setup, the wizard may skip some file creation // to avoid conflicts. This is acceptable behavior. // Let's check if the wizard at least completed by verifying package.json was updated const packageJsonPath = `${projectDir}/package.json`; (0, vitest_1.expect)(fs.existsSync(packageJsonPath)).toBe(true); const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf8'); const packageJson = JSON.parse(packageJsonContent); const hasSentryPackage = (packageJson.dependencies?.['@sentry/react-router']) || (packageJson.devDependencies?.['@sentry/react-router']); // The wizard should have at least installed the Sentry package (0, vitest_1.expect)(hasSentryPackage).toBeTruthy(); }); }); (0, vitest_1.describe)('missing entry files', () => { const integration = Constants_1.Integration.reactRouter; const projectDir = path.resolve(__dirname, '../test-applications/react-router-test-app-missing-entries'); (0, vitest_1.beforeAll)(async () => { // Copy project and remove entry files fs.cpSync(baseProjectDir, projectDir, { recursive: true }); const entryClientPath = path.join(projectDir, 'app', 'entry.client.tsx'); const entryServerPath = path.join(projectDir, 'app', 'entry.server.tsx'); if (fs.existsSync(entryClientPath)) fs.unlinkSync(entryClientPath); if (fs.existsSync(entryServerPath)) fs.unlinkSync(entryServerPath); await runWizardOnReactRouterProject(projectDir, integration); }); (0, vitest_1.afterAll)(() => { (0, utils_1.revertLocalChanges)(projectDir); (0, utils_1.cleanupGit)(projectDir); try { fs.rmSync(projectDir, { recursive: true, force: true }); } catch (e) { // Ignore cleanup errors } }); (0, vitest_1.test)('wizard creates missing entry files', () => { (0, utils_1.checkFileExists)(`${projectDir}/app/entry.client.tsx`); (0, utils_1.checkFileExists)(`${projectDir}/app/entry.server.tsx`); }); (0, vitest_1.test)('basic configuration still works', () => { (0, utils_1.checkPackageJson)(projectDir, integration); (0, utils_1.checkFileExists)(`${projectDir}/instrument.server.mjs`); }); }); }); }); //# sourceMappingURL=react-router.test.js.map