@sentry/wizard
Version:
Sentry wizard helping you to configure your project
235 lines (225 loc) • 10.2 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;
};
Object.defineProperty(exports, "__esModule", { value: true });
const path = __importStar(require("node:path"));
const Constants_1 = require("../../lib/Constants");
const utils_1 = require("../utils");
const vitest_1 = require("vitest");
const SERVER_TEMPLATE = `import { createRequestHandler } from '@remix-run/express';
import { installGlobals } from '@remix-run/node';
import compression from 'compression';
import express from 'express';
import morgan from 'morgan';
installGlobals();
const viteDevServer =
process.env.NODE_ENV === 'production'
? undefined
: await import('vite').then(vite =>
vite.createServer({
server: { middlewareMode: true },
}),
);
const app = express();
app.use(compression());
app.disable('x-powered-by');
if (viteDevServer) {
app.use(viteDevServer.middlewares);
} else {
app.use('/assets', express.static('build/client/assets', { immutable: true, maxAge: '1y' }));
}
app.use(express.static('build/client', { maxAge: '1h' }));
app.use(morgan('tiny'));
app.all(
'*',
createRequestHandler({
build: viteDevServer
? () => viteDevServer.ssrLoadModule('virtual:remix/server-build')
: await import('./build/server/index.js'),
}),
);
app.listen(0, () => console.log('Express server listening'));
`;
async function runWizardOnRemixProject(projectDir, integration, fileModificationFn) {
const wizardInstance = (0, utils_1.startWizardInstance)(integration, projectDir);
let packageManagerPrompted = false;
if (fileModificationFn) {
fileModificationFn(projectDir, integration);
await wizardInstance.waitForOutput('Do you want to continue anyway?');
packageManagerPrompted = await wizardInstance.sendStdinAndWaitForOutput([utils_1.KEYS.ENTER], 'Please select your package manager.');
}
else {
packageManagerPrompted = await wizardInstance.waitForOutput('Please select your package manager.');
}
const tracingOptionPrompted = packageManagerPrompted &&
(await wizardInstance.sendStdinAndWaitForOutput(
// Selecting `yarn` as the package manager
[utils_1.KEYS.DOWN, utils_1.KEYS.ENTER],
// "Do you want to enable Tracing", sometimes doesn't work as `Tracing` can be printed in bold.
'to track the performance of your application?', {
timeout: 240000,
}));
const replayOptionPrompted = tracingOptionPrompted &&
(await wizardInstance.sendStdinAndWaitForOutput([utils_1.KEYS.ENTER],
// "Do you want to enable Sentry Session Replay", sometimes doesn't work as `Sentry Session Replay` can be printed in bold.
'to get a video-like reproduction of errors during a user session?'));
const logOptionPrompted = replayOptionPrompted &&
(await wizardInstance.sendStdinAndWaitForOutput([utils_1.KEYS.ENTER],
// "Do you want to enable Logs", sometimes doesn't work as `Logs` can be printed in bold.
'to send your application logs to Sentry?'));
const examplePagePrompted = logOptionPrompted &&
(await wizardInstance.sendStdinAndWaitForOutput([utils_1.KEYS.ENTER], 'Do you want to create an example page', {
optional: true,
}));
// After the example page prompt, we send ENTER to accept it
// Then handle the MCP prompt that comes after
const mcpPrompted = examplePagePrompted &&
(await wizardInstance.sendStdinAndWaitForOutput([utils_1.KEYS.ENTER], // This ENTER is for accepting the example page
'Optionally add a project-scoped MCP server configuration for the Sentry MCP?', {
optional: true,
}));
// Decline MCP config (default is Yes, so press DOWN then ENTER to select No)
if (mcpPrompted) {
await wizardInstance.sendStdinAndWaitForOutput([utils_1.KEYS.DOWN, utils_1.KEYS.ENTER], 'Sentry has been successfully configured for your Remix project');
}
else {
// If MCP wasn't prompted, wait for success message directly
await wizardInstance.waitForOutput('Sentry has been successfully configured for your Remix project');
}
wizardInstance.kill();
}
function checkRemixProject(projectDir, integration, options) {
(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)('instrumentation.server file exists', () => {
(0, utils_1.checkFileExists)(`${projectDir}/instrumentation.server.mjs`);
});
(0, vitest_1.test)('entry.client file contains Sentry initialization', () => {
(0, utils_1.checkFileContents)(`${projectDir}/app/entry.client.tsx`, [
'import { init, replayIntegration, browserTracingIntegration } from "@sentry/remix";',
`init({
dsn: "${utils_1.TEST_ARGS.PROJECT_DSN}",
tracesSampleRate: 1,
enableLogs: true,
integrations: [browserTracingIntegration({
useEffect,
useLocation,
useMatches
}), replayIntegration({
maskAllText: true,
blockAllMedia: true
})],
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1,
sendDefaultPii: true
})`,
]);
});
(0, vitest_1.test)('entry.server file contains Sentry code', () => {
(0, utils_1.checkFileContents)(`${projectDir}/app/entry.server.tsx`, [
'import * as Sentry from "@sentry/remix";',
`export const handleError = Sentry.wrapHandleErrorWithSentry((error, { request }) => {
// Custom handleError implementation
});`,
]);
});
(0, vitest_1.test)('instrumentation.server file contains Sentry initialization', () => {
(0, utils_1.checkFileContents)(`${projectDir}/instrumentation.server.mjs`, [
'import * as Sentry from "@sentry/remix";',
`Sentry.init({
dsn: "${utils_1.TEST_ARGS.PROJECT_DSN}",
tracesSampleRate: 1,
enableLogs: true
})`,
]);
});
(0, vitest_1.test)('root file contains Sentry ErrorBoundary and withSentry wrapper', () => {
(0, utils_1.checkFileContents)(`${projectDir}/app/root.tsx`, [
'import { captureRemixErrorBoundaryError, withSentry } from "@sentry/remix";',
`export const ErrorBoundary = () => {
const error = useRouteError();
captureRemixErrorBoundaryError(error);
return <div>Something went wrong</div>;
};`,
`export default withSentry(App);`,
]);
});
(0, vitest_1.test)('builds successfully', async () => {
await (0, utils_1.checkIfBuilds)(projectDir);
});
(0, vitest_1.test)('runs on dev mode correctly', async () => {
await (0, utils_1.checkIfRunsOnDevMode)(projectDir, options?.devModeExpectedOutput || 'to expose');
});
(0, vitest_1.test)('runs on prod mode correctly', async () => {
await (0, utils_1.checkIfRunsOnProdMode)(projectDir, options?.prodModeExpectedOutput || '[remix-serve]');
});
}
(0, vitest_1.describe)('Remix', () => {
(0, vitest_1.describe)('with empty project', () => {
const integration = Constants_1.Integration.remix;
const projectDir = path.resolve(__dirname, '../test-applications/remix-test-app');
(0, vitest_1.beforeAll)(async () => {
await runWizardOnRemixProject(projectDir, integration);
});
(0, vitest_1.afterAll)(() => {
(0, utils_1.revertLocalChanges)(projectDir);
(0, utils_1.cleanupGit)(projectDir);
});
checkRemixProject(projectDir, integration);
});
(0, vitest_1.describe)('with existing custom Express server', () => {
const integration = Constants_1.Integration.remix;
const projectDir = path.resolve(__dirname, '../test-applications/remix-test-app');
(0, vitest_1.beforeAll)(async () => {
await runWizardOnRemixProject(projectDir, integration, (projectDir) => {
(0, utils_1.createFile)(`${projectDir}/server.mjs`, SERVER_TEMPLATE);
(0, utils_1.modifyFile)(`${projectDir}/package.json`, {
'"start": "remix-serve ./build/server/index.js"': '"start": "node ./server.mjs"',
'"dev": "remix vite:dev"': '"dev": "node ./server.mjs"',
});
});
});
(0, vitest_1.afterAll)(() => {
(0, utils_1.revertLocalChanges)(projectDir);
(0, utils_1.cleanupGit)(projectDir);
});
checkRemixProject(projectDir, integration, {
devModeExpectedOutput: 'Express server listening',
prodModeExpectedOutput: 'Express server listening',
});
(0, vitest_1.test)('server.mjs contains instrumentation file import', () => {
(0, utils_1.checkFileContents)(`${projectDir}/server.mjs`, [
"import './instrumentation.server.mjs';",
]);
});
});
});
//# sourceMappingURL=remix.test.js.map