@sentry/wizard
Version:
Sentry wizard helping you to configure your project
274 lines (254 loc) • 17.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const vitest_1 = require("vitest");
// @ts-expect-error - magicast is ESM and TS complains about that. It works though
const magicast_1 = require("magicast");
const templates_1 = require("../../src/nextjs/templates");
const utils_1 = require("../../src/nextjs/utils");
vitest_1.vi.mock('../../src/utils/clack/mcp-config', () => ({
offerProjectScopedMcpConfig: vitest_1.vi.fn().mockResolvedValue(undefined),
}));
(0, vitest_1.describe)('Next.js wizard double wrap prevention', () => {
const mockWithSentryConfigOptionsTemplate = (0, templates_1.getWithSentryConfigOptionsTemplate)({
orgSlug: 'test-org',
projectSlug: 'test-project',
selfHosted: false,
sentryUrl: 'https://sentry.io',
tunnelRoute: false,
});
(0, vitest_1.describe)('unwrapSentryConfigAst utility function', () => {
(0, vitest_1.describe)('AST-based expression unwrapping', () => {
(0, vitest_1.it)('keeps code without withSentryConfig', () => {
const mod = (0, magicast_1.parseModule)('export default nextConfig');
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access
const originalAST = mod.exports.default.$ast;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const resultAST = (0, utils_1.unwrapSentryConfigAst)(originalAST);
(0, vitest_1.expect)(resultAST).toBe(originalAST);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { code: exportDefaultCode } = (0, magicast_1.generateCode)({ $ast: resultAST });
(0, vitest_1.expect)(exportDefaultCode).toMatchInlineSnapshot(`"nextConfig"`);
});
(0, vitest_1.it)('should handle plain object literal exports', () => {
const mod = (0, magicast_1.parseModule)(`export default { nextConfig: { randomValue: true } }`);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access
const originalAST = mod.exports.default.$ast;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const resultAST = (0, utils_1.unwrapSentryConfigAst)(originalAST);
(0, vitest_1.expect)(resultAST).toBe(originalAST);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(0, vitest_1.expect)(resultAST.type).toBe('ObjectExpression');
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(0, vitest_1.expect)(resultAST.properties).toHaveLength(1);
});
(0, vitest_1.it)('should unwrap withSentryConfig with options', () => {
const mod = (0, magicast_1.parseModule)('export default withSentryConfig(nextConfig, { org: "test" })');
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access
const wrappedAst = mod.exports.default.$ast;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const resultAST = (0, utils_1.unwrapSentryConfigAst)(wrappedAst);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access
const { code: exportDefaultCode } = (0, magicast_1.generateCode)({ $ast: resultAST });
(0, vitest_1.expect)(exportDefaultCode).toMatchInlineSnapshot(`"nextConfig"`);
});
(0, vitest_1.it)('should unwrap withSentryConfig without options', () => {
const mod = (0, magicast_1.parseModule)('export default withSentryConfig(nextConfig)');
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access
const wrappedAst = mod.exports.default.$ast;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const resultAST = (0, utils_1.unwrapSentryConfigAst)(wrappedAst);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access
const { code: exportDefaultCode } = (0, magicast_1.generateCode)({ $ast: resultAST });
(0, vitest_1.expect)(exportDefaultCode).toMatchInlineSnapshot(`"nextConfig"`);
});
(0, vitest_1.it)('should handle nested withSentryConfig calls', () => {
const mod = (0, magicast_1.parseModule)('export default withSentryConfig(withSentryConfig(nextConfig, { org: "inner" }), { org: "outer" })');
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access
const wrappedAst = mod.exports.default.$ast;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const resultAST = (0, utils_1.unwrapSentryConfigAst)(wrappedAst);
// Should unwrap one level and return the inner withSentryConfig call
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(0, vitest_1.expect)(resultAST.type).toBe('CallExpression');
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(0, vitest_1.expect)(resultAST.callee.name).toBe('withSentryConfig');
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access
const { code: exportDefaultCode } = (0, magicast_1.generateCode)({ $ast: resultAST });
(0, vitest_1.expect)(exportDefaultCode).toMatchInlineSnapshot(`"withSentryConfig(nextConfig, { org: "inner" })"`);
});
(0, vitest_1.it)('should handle complex expressions', () => {
const mod = (0, magicast_1.parseModule)('export default withSentryConfig(someComplexExpression.withMethods())');
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access
const wrappedAst = mod.exports.default.$ast;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access
const resultAST = (0, utils_1.unwrapSentryConfigAst)(wrappedAst);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access
const { code: exportDefaultCode } = (0, magicast_1.generateCode)({ $ast: resultAST });
(0, vitest_1.expect)(exportDefaultCode).toMatchInlineSnapshot(`"someComplexExpression.withMethods()"`);
});
(0, vitest_1.it)('should handle object literals', () => {
const mod = (0, magicast_1.parseModule)('export default withSentryConfig({ next: "config", obj: { next: "nested" } }, { org: "test" })');
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access
const wrappedAst = mod.exports.default.$ast;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access
const resultAST = (0, utils_1.unwrapSentryConfigAst)(wrappedAst);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access
const { code: exportDefaultCode } = (0, magicast_1.generateCode)({ $ast: resultAST });
(0, vitest_1.expect)(exportDefaultCode).toMatchInlineSnapshot(`"{ next: "config", obj: { next: "nested" } }"`);
});
(0, vitest_1.it)('should return unchanged if not a withSentryConfig call', () => {
const mod = (0, magicast_1.parseModule)('export default someOtherFunction(nextConfig)');
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access
const originalAST = mod.exports.default.$ast;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const resultAST = (0, utils_1.unwrapSentryConfigAst)(originalAST);
(0, vitest_1.expect)(resultAST).toBe(originalAST);
});
});
});
(0, vitest_1.describe)('MJS/TS files', () => {
(0, vitest_1.it)('should unwrap existing withSentryConfig and re-wrap with new config using AST', () => {
const existingMjsContent = `import { withSentryConfig } from "@sentry/nextjs";
const nextConfig = {};
export default withSentryConfig(nextConfig, {
org: "old-org",
project: "old-project",
});`;
const mod = (0, magicast_1.parseModule)(existingMjsContent);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access
const originalAST = mod.exports.default.$ast;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const unwrappedAst = (0, utils_1.unwrapSentryConfigAst)(originalAST);
// Verify it returns the first argument (nextConfig identifier)
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(0, vitest_1.expect)(unwrappedAst.type).toBe('Identifier');
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(0, vitest_1.expect)(unwrappedAst.name).toBe('nextConfig');
// Create a fresh module to simulate the re-wrapping process
const freshMod = (0, magicast_1.parseModule)(`import { withSentryConfig } from "@sentry/nextjs";
const nextConfig = {};
export default nextConfig;`);
// Apply wrapping
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
freshMod.exports.default = (0, utils_1.wrapWithSentryConfig)(freshMod.exports.default, mockWithSentryConfigOptionsTemplate);
const newCode = freshMod.generate().code;
// Verify only one withSentryConfig call exists
const withSentryConfigMatches = newCode.match(/withSentryConfig\s*\(/g);
(0, vitest_1.expect)(withSentryConfigMatches).toHaveLength(1);
(0, vitest_1.expect)(newCode).toContain('test-org');
(0, vitest_1.expect)(newCode).toContain('test-project');
(0, vitest_1.expect)(newCode).not.toContain('old-org');
(0, vitest_1.expect)(newCode).not.toContain('old-project');
});
(0, vitest_1.it)('should handle complex nested configurations using AST', () => {
const existingMjsContent = `import { withSentryConfig } from "@sentry/nextjs";
const nextConfig = { experimental: { appDir: true } };
export default withSentryConfig(
withSentryConfig(nextConfig, { org: "nested-org" }),
{ org: "outer-org" }
);`;
const mod = (0, magicast_1.parseModule)(existingMjsContent);
// First unwrap ----
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access
const firstUnwrapAST = (0, utils_1.unwrapSentryConfigAst)(mod.exports.default.$ast);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(0, vitest_1.expect)(firstUnwrapAST.type).toBe('CallExpression');
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(0, vitest_1.expect)(firstUnwrapAST.callee.name).toBe('withSentryConfig');
const { code: exportDefaultCode1 } = (0, magicast_1.generateCode)({
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
$ast: firstUnwrapAST,
});
(0, vitest_1.expect)(exportDefaultCode1).toMatchInlineSnapshot(`"withSentryConfig(nextConfig, { org: "nested-org" })"`);
// Second unwrap ----
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const secondUnwrapAST = (0, utils_1.unwrapSentryConfigAst)(firstUnwrapAST);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(0, vitest_1.expect)(secondUnwrapAST.type).toBe('Identifier');
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(0, vitest_1.expect)(secondUnwrapAST.name).toBe('nextConfig');
const { code: exportDefaultCode2 } = (0, magicast_1.generateCode)({
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
$ast: secondUnwrapAST,
});
(0, vitest_1.expect)(exportDefaultCode2).toMatchInlineSnapshot(`"nextConfig"`);
});
(0, vitest_1.it)('should handle simple export without existing withSentryConfig using AST', () => {
const simpleMjsContent = `const nextConfig = {};
export default nextConfig;`;
const mod = (0, magicast_1.parseModule)(simpleMjsContent);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access
const originalAST = mod.exports.default.$ast;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const unwrappedAst = (0, utils_1.unwrapSentryConfigAst)(originalAST);
(0, vitest_1.expect)(unwrappedAst).toBe(originalAST);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
mod.exports.default = (0, utils_1.wrapWithSentryConfig)(mod.exports.default, mockWithSentryConfigOptionsTemplate);
const newCode = mod.generate().code;
// Should have exactly one withSentryConfig call
const withSentryConfigMatches = newCode.match(/withSentryConfig\s*\(/g);
(0, vitest_1.expect)(withSentryConfigMatches).toHaveLength(1);
(0, vitest_1.expect)(newCode).toMatchInlineSnapshot(`
"const nextConfig = {};
export default withSentryConfig(nextConfig, ${sentryOptionsSnapshot});"
`);
});
(0, vitest_1.it)('should handle withSentryConfig(nextConfig) without options using AST', () => {
const existingMjsContent = `import { withSentryConfig } from "@sentry/nextjs";
const nextConfig = {};
export default withSentryConfig(nextConfig);`;
const mod = (0, magicast_1.parseModule)(existingMjsContent);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access
const originalAST = mod.exports.default.$ast;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const unwrappedAst = (0, utils_1.unwrapSentryConfigAst)(originalAST);
// Verify it returns the first argument (nextConfig identifier)
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(0, vitest_1.expect)(unwrappedAst.type).toBe('Identifier');
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(0, vitest_1.expect)(unwrappedAst.name).toBe('nextConfig');
// Simulate the re-wrapping process
const freshMod = (0, magicast_1.parseModule)(`import { withSentryConfig } from "@sentry/nextjs";
const nextConfig = {};
export default nextConfig;`);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
freshMod.exports.default = (0, utils_1.wrapWithSentryConfig)(freshMod.exports.default, mockWithSentryConfigOptionsTemplate);
const newCode = freshMod.generate().code;
// Should have exactly one withSentryConfig call
const withSentryConfigMatches = newCode.match(/withSentryConfig\s*\(/g);
(0, vitest_1.expect)(withSentryConfigMatches).toHaveLength(1);
(0, vitest_1.expect)(newCode).not.toContain('withSentryConfig(withSentryConfig(');
(0, vitest_1.expect)(newCode).toMatch(/withSentryConfig\s*\(\s*nextConfig\s*,/);
});
});
});
const sentryOptionsSnapshot = `{
// For all available options, see:
// https://www.npmjs.com/package/@sentry/webpack-plugin#options
org: "test-org",
project: "test-project",
// Only print logs for uploading source maps in CI
silent: !process.env.CI,
// For all available options, see:
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
// Upload a larger set of source maps for prettier stack traces (increases build time)
widenClientFileUpload: true,
// Uncomment to route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers.
// This can increase your server load as well as your hosting bill.
// Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client-
// side errors will fail.
// tunnelRoute: "/monitoring",
webpack: {
// Enables automatic instrumentation of Vercel Cron Monitors. (Does not yet work with App Router route handlers.)
// See the following for more information:
// https://docs.sentry.io/product/crons/
// https://vercel.com/docs/cron-jobs
automaticVercelMonitors: true,
// Tree-shaking options for reducing bundle size
treeshake: {
// Automatically tree-shake Sentry logger statements to reduce bundle size
removeDebugLogging: true,
},
}
}`;
//# sourceMappingURL=wizard-double-wrap-prevention.test.js.map